001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.broker;
018
019import java.io.EOFException;
020import java.io.IOException;
021import java.net.SocketException;
022import java.net.URI;
023import java.util.Collection;
024import java.util.HashMap;
025import java.util.Iterator;
026import java.util.LinkedList;
027import java.util.List;
028import java.util.Map;
029import java.util.Properties;
030import java.util.concurrent.ConcurrentHashMap;
031import java.util.concurrent.CopyOnWriteArrayList;
032import java.util.concurrent.CountDownLatch;
033import java.util.concurrent.TimeUnit;
034import java.util.concurrent.atomic.AtomicBoolean;
035import java.util.concurrent.atomic.AtomicInteger;
036import java.util.concurrent.atomic.AtomicReference;
037import java.util.concurrent.locks.ReentrantReadWriteLock;
038
039import javax.transaction.xa.XAResource;
040
041import org.apache.activemq.advisory.AdvisorySupport;
042import org.apache.activemq.broker.region.ConnectionStatistics;
043import org.apache.activemq.broker.region.RegionBroker;
044import org.apache.activemq.command.ActiveMQDestination;
045import org.apache.activemq.command.BrokerInfo;
046import org.apache.activemq.command.Command;
047import org.apache.activemq.command.CommandTypes;
048import org.apache.activemq.command.ConnectionControl;
049import org.apache.activemq.command.ConnectionError;
050import org.apache.activemq.command.ConnectionId;
051import org.apache.activemq.command.ConnectionInfo;
052import org.apache.activemq.command.ConsumerControl;
053import org.apache.activemq.command.ConsumerId;
054import org.apache.activemq.command.ConsumerInfo;
055import org.apache.activemq.command.ControlCommand;
056import org.apache.activemq.command.DataArrayResponse;
057import org.apache.activemq.command.DestinationInfo;
058import org.apache.activemq.command.ExceptionResponse;
059import org.apache.activemq.command.FlushCommand;
060import org.apache.activemq.command.IntegerResponse;
061import org.apache.activemq.command.KeepAliveInfo;
062import org.apache.activemq.command.Message;
063import org.apache.activemq.command.MessageAck;
064import org.apache.activemq.command.MessageDispatch;
065import org.apache.activemq.command.MessageDispatchNotification;
066import org.apache.activemq.command.MessagePull;
067import org.apache.activemq.command.ProducerAck;
068import org.apache.activemq.command.ProducerId;
069import org.apache.activemq.command.ProducerInfo;
070import org.apache.activemq.command.RemoveInfo;
071import org.apache.activemq.command.RemoveSubscriptionInfo;
072import org.apache.activemq.command.Response;
073import org.apache.activemq.command.SessionId;
074import org.apache.activemq.command.SessionInfo;
075import org.apache.activemq.command.ShutdownInfo;
076import org.apache.activemq.command.TransactionId;
077import org.apache.activemq.command.TransactionInfo;
078import org.apache.activemq.command.WireFormatInfo;
079import org.apache.activemq.network.DemandForwardingBridge;
080import org.apache.activemq.network.MBeanNetworkListener;
081import org.apache.activemq.network.NetworkBridgeConfiguration;
082import org.apache.activemq.network.NetworkBridgeFactory;
083import org.apache.activemq.security.MessageAuthorizationPolicy;
084import org.apache.activemq.state.CommandVisitor;
085import org.apache.activemq.state.ConnectionState;
086import org.apache.activemq.state.ConsumerState;
087import org.apache.activemq.state.ProducerState;
088import org.apache.activemq.state.SessionState;
089import org.apache.activemq.state.TransactionState;
090import org.apache.activemq.thread.Task;
091import org.apache.activemq.thread.TaskRunner;
092import org.apache.activemq.thread.TaskRunnerFactory;
093import org.apache.activemq.transaction.Transaction;
094import org.apache.activemq.transport.DefaultTransportListener;
095import org.apache.activemq.transport.ResponseCorrelator;
096import org.apache.activemq.transport.TransmitCallback;
097import org.apache.activemq.transport.Transport;
098import org.apache.activemq.transport.TransportDisposedIOException;
099import org.apache.activemq.util.IntrospectionSupport;
100import org.apache.activemq.util.MarshallingSupport;
101import org.slf4j.Logger;
102import org.slf4j.LoggerFactory;
103import org.slf4j.MDC;
104
105public class TransportConnection implements Connection, Task, CommandVisitor {
106    private static final Logger LOG = LoggerFactory.getLogger(TransportConnection.class);
107    private static final Logger TRANSPORTLOG = LoggerFactory.getLogger(TransportConnection.class.getName() + ".Transport");
108    private static final Logger SERVICELOG = LoggerFactory.getLogger(TransportConnection.class.getName() + ".Service");
109    // Keeps track of the broker and connector that created this connection.
110    protected final Broker broker;
111    protected final TransportConnector connector;
112    // Keeps track of the state of the connections.
113    // protected final ConcurrentHashMap localConnectionStates=new
114    // ConcurrentHashMap();
115    protected final Map<ConnectionId, ConnectionState> brokerConnectionStates;
116    // The broker and wireformat info that was exchanged.
117    protected BrokerInfo brokerInfo;
118    protected final List<Command> dispatchQueue = new LinkedList<Command>();
119    protected TaskRunner taskRunner;
120    protected final AtomicReference<Throwable> transportException = new AtomicReference<Throwable>();
121    protected AtomicBoolean dispatchStopped = new AtomicBoolean(false);
122    private final Transport transport;
123    private MessageAuthorizationPolicy messageAuthorizationPolicy;
124    private WireFormatInfo wireFormatInfo;
125    // Used to do async dispatch.. this should perhaps be pushed down into the
126    // transport layer..
127    private boolean inServiceException;
128    private final ConnectionStatistics statistics = new ConnectionStatistics();
129    private boolean manageable;
130    private boolean slow;
131    private boolean markedCandidate;
132    private boolean blockedCandidate;
133    private boolean blocked;
134    private boolean connected;
135    private boolean active;
136    private boolean starting;
137    private boolean pendingStop;
138    private long timeStamp;
139    private final AtomicBoolean stopping = new AtomicBoolean(false);
140    private final CountDownLatch stopped = new CountDownLatch(1);
141    private final AtomicBoolean asyncException = new AtomicBoolean(false);
142    private final Map<ProducerId, ProducerBrokerExchange> producerExchanges = new HashMap<ProducerId, ProducerBrokerExchange>();
143    private final Map<ConsumerId, ConsumerBrokerExchange> consumerExchanges = new HashMap<ConsumerId, ConsumerBrokerExchange>();
144    private final CountDownLatch dispatchStoppedLatch = new CountDownLatch(1);
145    private ConnectionContext context;
146    private boolean networkConnection;
147    private boolean faultTolerantConnection;
148    private final AtomicInteger protocolVersion = new AtomicInteger(CommandTypes.PROTOCOL_VERSION);
149    private DemandForwardingBridge duplexBridge;
150    private final TaskRunnerFactory taskRunnerFactory;
151    private final TaskRunnerFactory stopTaskRunnerFactory;
152    private TransportConnectionStateRegister connectionStateRegister = new SingleTransportConnectionStateRegister();
153    private final ReentrantReadWriteLock serviceLock = new ReentrantReadWriteLock();
154    private String duplexNetworkConnectorId;
155
156    /**
157     * @param taskRunnerFactory - can be null if you want direct dispatch to the transport
158     *                          else commands are sent async.
159     * @param stopTaskRunnerFactory - can <b>not</b> be null, used for stopping this connection.
160     */
161    public TransportConnection(TransportConnector connector, final Transport transport, Broker broker,
162                               TaskRunnerFactory taskRunnerFactory, TaskRunnerFactory stopTaskRunnerFactory) {
163        this.connector = connector;
164        this.broker = broker;
165        RegionBroker rb = (RegionBroker) broker.getAdaptor(RegionBroker.class);
166        brokerConnectionStates = rb.getConnectionStates();
167        if (connector != null) {
168            this.statistics.setParent(connector.getStatistics());
169            this.messageAuthorizationPolicy = connector.getMessageAuthorizationPolicy();
170        }
171        this.taskRunnerFactory = taskRunnerFactory;
172        this.stopTaskRunnerFactory = stopTaskRunnerFactory;
173        this.transport = transport;
174        final BrokerService brokerService = this.broker.getBrokerService();
175        if( this.transport instanceof BrokerServiceAware ) {
176            ((BrokerServiceAware)this.transport).setBrokerService(brokerService);
177        }
178        this.transport.setTransportListener(new DefaultTransportListener() {
179            @Override
180            public void onCommand(Object o) {
181                serviceLock.readLock().lock();
182                try {
183                    if (!(o instanceof Command)) {
184                        throw new RuntimeException("Protocol violation - Command corrupted: " + o.toString());
185                    }
186                    Command command = (Command) o;
187                    if (!brokerService.isStopping()) {
188                        Response response = service(command);
189                        if (response != null && !brokerService.isStopping()) {
190                            dispatchSync(response);
191                        }
192                    } else {
193                        throw new BrokerStoppedException("Broker " + brokerService + " is being stopped");
194                    }
195                } finally {
196                    serviceLock.readLock().unlock();
197                }
198            }
199
200            @Override
201            public void onException(IOException exception) {
202                serviceLock.readLock().lock();
203                try {
204                    serviceTransportException(exception);
205                } finally {
206                    serviceLock.readLock().unlock();
207                }
208            }
209        });
210        connected = true;
211    }
212
213    /**
214     * Returns the number of messages to be dispatched to this connection
215     *
216     * @return size of dispatch queue
217     */
218    @Override
219    public int getDispatchQueueSize() {
220        synchronized (dispatchQueue) {
221            return dispatchQueue.size();
222        }
223    }
224
225    public void serviceTransportException(IOException e) {
226        BrokerService bService = connector.getBrokerService();
227        if (bService.isShutdownOnSlaveFailure()) {
228            if (brokerInfo != null) {
229                if (brokerInfo.isSlaveBroker()) {
230                    LOG.error("Slave has exception: {} shutting down master now.", e.getMessage(), e);
231                    try {
232                        doStop();
233                        bService.stop();
234                    } catch (Exception ex) {
235                        LOG.warn("Failed to stop the master", ex);
236                    }
237                }
238            }
239        }
240        if (!stopping.get() && !pendingStop) {
241            transportException.set(e);
242            e.printStackTrace();
243            if (TRANSPORTLOG.isDebugEnabled()) {
244                TRANSPORTLOG.debug(this + " failed: " + e, e);
245            } else if (TRANSPORTLOG.isWarnEnabled() && !expected(e)) {
246                TRANSPORTLOG.warn(this + " failed: " + e);
247            }
248            stopAsync(e);
249        }
250    }
251
252    private boolean expected(IOException e) {
253        return isStomp() && ((e instanceof SocketException && e.getMessage().indexOf("reset") != -1) || e instanceof EOFException);
254    }
255
256    private boolean isStomp() {
257        URI uri = connector.getUri();
258        return uri != null && uri.getScheme() != null && uri.getScheme().indexOf("stomp") != -1;
259    }
260
261    /**
262     * Calls the serviceException method in an async thread. Since handling a
263     * service exception closes a socket, we should not tie up broker threads
264     * since client sockets may hang or cause deadlocks.
265     */
266    @Override
267    public void serviceExceptionAsync(final IOException e) {
268        if (asyncException.compareAndSet(false, true)) {
269            new Thread("Async Exception Handler") {
270                @Override
271                public void run() {
272                    serviceException(e);
273                }
274            }.start();
275        }
276    }
277
278    /**
279     * Closes a clients connection due to a detected error. Errors are ignored
280     * if: the client is closing or broker is closing. Otherwise, the connection
281     * error transmitted to the client before stopping it's transport.
282     */
283    @Override
284    public void serviceException(Throwable e) {
285        // are we a transport exception such as not being able to dispatch
286        // synchronously to a transport
287        if (e instanceof IOException) {
288            serviceTransportException((IOException) e);
289        } else if (e.getClass() == BrokerStoppedException.class) {
290            // Handle the case where the broker is stopped
291            // But the client is still connected.
292            if (!stopping.get()) {
293                SERVICELOG.debug("Broker has been stopped.  Notifying client and closing his connection.");
294                ConnectionError ce = new ConnectionError();
295                ce.setException(e);
296                dispatchSync(ce);
297                // Record the error that caused the transport to stop
298                transportException.set(e);
299                // Wait a little bit to try to get the output buffer to flush
300                // the exception notification to the client.
301                try {
302                    Thread.sleep(500);
303                } catch (InterruptedException ie) {
304                    Thread.currentThread().interrupt();
305                }
306                // Worst case is we just kill the connection before the
307                // notification gets to him.
308                stopAsync();
309            }
310        } else if (!stopping.get() && !inServiceException) {
311            inServiceException = true;
312            try {
313                if (SERVICELOG.isDebugEnabled()) {
314                    SERVICELOG.debug("Async error occurred: " + e, e);
315                } else {
316                    SERVICELOG.warn("Async error occurred: " + e);
317                }
318                ConnectionError ce = new ConnectionError();
319                ce.setException(e);
320                if (pendingStop) {
321                    dispatchSync(ce);
322                } else {
323                    dispatchAsync(ce);
324                }
325            } finally {
326                inServiceException = false;
327            }
328        }
329    }
330
331    @Override
332    public Response service(Command command) {
333        MDC.put("activemq.connector", connector.getUri().toString());
334        Response response = null;
335        boolean responseRequired = command.isResponseRequired();
336        int commandId = command.getCommandId();
337        try {
338            if (!pendingStop) {
339                response = command.visit(this);
340            } else {
341                response = new ExceptionResponse(transportException.get());
342            }
343        } catch (Throwable e) {
344            if (SERVICELOG.isDebugEnabled() && e.getClass() != BrokerStoppedException.class) {
345                SERVICELOG.debug("Error occured while processing " + (responseRequired ? "sync" : "async")
346                        + " command: " + command + ", exception: " + e, e);
347            }
348
349            if (e instanceof SuppressReplyException || (e.getCause() instanceof SuppressReplyException)) {
350                LOG.info("Suppressing reply to: " + command + " on: " + e + ", cause: " + e.getCause());
351                responseRequired = false;
352            }
353
354            if (responseRequired) {
355                if (e instanceof SecurityException || e.getCause() instanceof SecurityException) {
356                    SERVICELOG.warn("Security Error occurred on connection to: {}, {}",
357                            transport.getRemoteAddress(), e.getMessage());
358                }
359                response = new ExceptionResponse(e);
360            } else {
361                serviceException(e);
362            }
363        }
364        if (responseRequired) {
365            if (response == null) {
366                response = new Response();
367            }
368            response.setCorrelationId(commandId);
369        }
370        // The context may have been flagged so that the response is not
371        // sent.
372        if (context != null) {
373            if (context.isDontSendReponse()) {
374                context.setDontSendReponse(false);
375                response = null;
376            }
377            context = null;
378        }
379        MDC.remove("activemq.connector");
380        return response;
381    }
382
383    @Override
384    public Response processKeepAlive(KeepAliveInfo info) throws Exception {
385        return null;
386    }
387
388    @Override
389    public Response processRemoveSubscription(RemoveSubscriptionInfo info) throws Exception {
390        broker.removeSubscription(lookupConnectionState(info.getConnectionId()).getContext(), info);
391        return null;
392    }
393
394    @Override
395    public Response processWireFormat(WireFormatInfo info) throws Exception {
396        wireFormatInfo = info;
397        protocolVersion.set(info.getVersion());
398        return null;
399    }
400
401    @Override
402    public Response processShutdown(ShutdownInfo info) throws Exception {
403        stopAsync();
404        return null;
405    }
406
407    @Override
408    public Response processFlush(FlushCommand command) throws Exception {
409        return null;
410    }
411
412    @Override
413    public Response processBeginTransaction(TransactionInfo info) throws Exception {
414        TransportConnectionState cs = lookupConnectionState(info.getConnectionId());
415        context = null;
416        if (cs != null) {
417            context = cs.getContext();
418        }
419        if (cs == null) {
420            throw new NullPointerException("Context is null");
421        }
422        // Avoid replaying dup commands
423        if (cs.getTransactionState(info.getTransactionId()) == null) {
424            cs.addTransactionState(info.getTransactionId());
425            broker.beginTransaction(context, info.getTransactionId());
426        }
427        return null;
428    }
429
430    @Override
431    public int getActiveTransactionCount() {
432        int rc = 0;
433        for (TransportConnectionState cs : connectionStateRegister.listConnectionStates()) {
434            Collection<TransactionState> transactions = cs.getTransactionStates();
435            for (TransactionState transaction : transactions) {
436                rc++;
437            }
438        }
439        return rc;
440    }
441
442    @Override
443    public Long getOldestActiveTransactionDuration() {
444        TransactionState oldestTX = null;
445        for (TransportConnectionState cs : connectionStateRegister.listConnectionStates()) {
446            Collection<TransactionState> transactions = cs.getTransactionStates();
447            for (TransactionState transaction : transactions) {
448                if( oldestTX ==null || oldestTX.getCreatedAt() < transaction.getCreatedAt() ) {
449                    oldestTX = transaction;
450                }
451            }
452        }
453        if( oldestTX == null ) {
454            return null;
455        }
456        return System.currentTimeMillis() - oldestTX.getCreatedAt();
457    }
458
459    @Override
460    public Response processEndTransaction(TransactionInfo info) throws Exception {
461        // No need to do anything. This packet is just sent by the client
462        // make sure he is synced with the server as commit command could
463        // come from a different connection.
464        return null;
465    }
466
467    @Override
468    public Response processPrepareTransaction(TransactionInfo info) throws Exception {
469        TransportConnectionState cs = lookupConnectionState(info.getConnectionId());
470        context = null;
471        if (cs != null) {
472            context = cs.getContext();
473        }
474        if (cs == null) {
475            throw new NullPointerException("Context is null");
476        }
477        TransactionState transactionState = cs.getTransactionState(info.getTransactionId());
478        if (transactionState == null) {
479            throw new IllegalStateException("Cannot prepare a transaction that had not been started or previously returned XA_RDONLY: "
480                    + info.getTransactionId());
481        }
482        // Avoid dups.
483        if (!transactionState.isPrepared()) {
484            transactionState.setPrepared(true);
485            int result = broker.prepareTransaction(context, info.getTransactionId());
486            transactionState.setPreparedResult(result);
487            if (result == XAResource.XA_RDONLY) {
488                // we are done, no further rollback or commit from TM
489                cs.removeTransactionState(info.getTransactionId());
490            }
491            IntegerResponse response = new IntegerResponse(result);
492            return response;
493        } else {
494            IntegerResponse response = new IntegerResponse(transactionState.getPreparedResult());
495            return response;
496        }
497    }
498
499    @Override
500    public Response processCommitTransactionOnePhase(TransactionInfo info) throws Exception {
501        TransportConnectionState cs = lookupConnectionState(info.getConnectionId());
502        context = cs.getContext();
503        cs.removeTransactionState(info.getTransactionId());
504        broker.commitTransaction(context, info.getTransactionId(), true);
505        return null;
506    }
507
508    @Override
509    public Response processCommitTransactionTwoPhase(TransactionInfo info) throws Exception {
510        TransportConnectionState cs = lookupConnectionState(info.getConnectionId());
511        context = cs.getContext();
512        cs.removeTransactionState(info.getTransactionId());
513        broker.commitTransaction(context, info.getTransactionId(), false);
514        return null;
515    }
516
517    @Override
518    public Response processRollbackTransaction(TransactionInfo info) throws Exception {
519        TransportConnectionState cs = lookupConnectionState(info.getConnectionId());
520        context = cs.getContext();
521        cs.removeTransactionState(info.getTransactionId());
522        broker.rollbackTransaction(context, info.getTransactionId());
523        return null;
524    }
525
526    @Override
527    public Response processForgetTransaction(TransactionInfo info) throws Exception {
528        TransportConnectionState cs = lookupConnectionState(info.getConnectionId());
529        context = cs.getContext();
530        broker.forgetTransaction(context, info.getTransactionId());
531        return null;
532    }
533
534    @Override
535    public Response processRecoverTransactions(TransactionInfo info) throws Exception {
536        TransportConnectionState cs = lookupConnectionState(info.getConnectionId());
537        context = cs.getContext();
538        TransactionId[] preparedTransactions = broker.getPreparedTransactions(context);
539        return new DataArrayResponse(preparedTransactions);
540    }
541
542    @Override
543    public Response processMessage(Message messageSend) throws Exception {
544        ProducerId producerId = messageSend.getProducerId();
545        ProducerBrokerExchange producerExchange = getProducerBrokerExchange(producerId);
546        if (producerExchange.canDispatch(messageSend)) {
547            broker.send(producerExchange, messageSend);
548        }
549        return null;
550    }
551
552    @Override
553    public Response processMessageAck(MessageAck ack) throws Exception {
554        ConsumerBrokerExchange consumerExchange = getConsumerBrokerExchange(ack.getConsumerId());
555        if (consumerExchange != null) {
556            broker.acknowledge(consumerExchange, ack);
557        } else if (ack.isInTransaction()) {
558            LOG.warn("no matching consumer, ignoring ack {}", consumerExchange, ack);
559        }
560        return null;
561    }
562
563    @Override
564    public Response processMessagePull(MessagePull pull) throws Exception {
565        return broker.messagePull(lookupConnectionState(pull.getConsumerId()).getContext(), pull);
566    }
567
568    @Override
569    public Response processMessageDispatchNotification(MessageDispatchNotification notification) throws Exception {
570        broker.processDispatchNotification(notification);
571        return null;
572    }
573
574    @Override
575    public Response processAddDestination(DestinationInfo info) throws Exception {
576        TransportConnectionState cs = lookupConnectionState(info.getConnectionId());
577        broker.addDestinationInfo(cs.getContext(), info);
578        if (info.getDestination().isTemporary()) {
579            cs.addTempDestination(info);
580        }
581        return null;
582    }
583
584    @Override
585    public Response processRemoveDestination(DestinationInfo info) throws Exception {
586        TransportConnectionState cs = lookupConnectionState(info.getConnectionId());
587        broker.removeDestinationInfo(cs.getContext(), info);
588        if (info.getDestination().isTemporary()) {
589            cs.removeTempDestination(info.getDestination());
590        }
591        return null;
592    }
593
594    @Override
595    public Response processAddProducer(ProducerInfo info) throws Exception {
596        SessionId sessionId = info.getProducerId().getParentId();
597        ConnectionId connectionId = sessionId.getParentId();
598        TransportConnectionState cs = lookupConnectionState(connectionId);
599        if (cs == null) {
600            throw new IllegalStateException("Cannot add a producer to a connection that had not been registered: "
601                    + connectionId);
602        }
603        SessionState ss = cs.getSessionState(sessionId);
604        if (ss == null) {
605            throw new IllegalStateException("Cannot add a producer to a session that had not been registered: "
606                    + sessionId);
607        }
608        // Avoid replaying dup commands
609        if (!ss.getProducerIds().contains(info.getProducerId())) {
610            ActiveMQDestination destination = info.getDestination();
611            // Do not check for null here as it would cause the count of max producers to exclude
612            // anonymous producers.  The isAdvisoryTopic method checks for null so it is safe to
613            // call it from here with a null Destination value.
614            if (!AdvisorySupport.isAdvisoryTopic(destination)) {
615                if (getProducerCount(connectionId) >= connector.getMaximumProducersAllowedPerConnection()){
616                    throw new IllegalStateException("Can't add producer on connection " + connectionId + ": at maximum limit: " + connector.getMaximumProducersAllowedPerConnection());
617                }
618            }
619            broker.addProducer(cs.getContext(), info);
620            try {
621                ss.addProducer(info);
622            } catch (IllegalStateException e) {
623                broker.removeProducer(cs.getContext(), info);
624            }
625
626        }
627        return null;
628    }
629
630    @Override
631    public Response processRemoveProducer(ProducerId id) throws Exception {
632        SessionId sessionId = id.getParentId();
633        ConnectionId connectionId = sessionId.getParentId();
634        TransportConnectionState cs = lookupConnectionState(connectionId);
635        SessionState ss = cs.getSessionState(sessionId);
636        if (ss == null) {
637            throw new IllegalStateException("Cannot remove a producer from a session that had not been registered: "
638                    + sessionId);
639        }
640        ProducerState ps = ss.removeProducer(id);
641        if (ps == null) {
642            throw new IllegalStateException("Cannot remove a producer that had not been registered: " + id);
643        }
644        removeProducerBrokerExchange(id);
645        broker.removeProducer(cs.getContext(), ps.getInfo());
646        return null;
647    }
648
649    @Override
650    public Response processAddConsumer(ConsumerInfo info) throws Exception {
651        SessionId sessionId = info.getConsumerId().getParentId();
652        ConnectionId connectionId = sessionId.getParentId();
653        TransportConnectionState cs = lookupConnectionState(connectionId);
654        if (cs == null) {
655            throw new IllegalStateException("Cannot add a consumer to a connection that had not been registered: "
656                    + connectionId);
657        }
658        SessionState ss = cs.getSessionState(sessionId);
659        if (ss == null) {
660            throw new IllegalStateException(broker.getBrokerName()
661                    + " Cannot add a consumer to a session that had not been registered: " + sessionId);
662        }
663        // Avoid replaying dup commands
664        if (!ss.getConsumerIds().contains(info.getConsumerId())) {
665            ActiveMQDestination destination = info.getDestination();
666            if (destination != null && !AdvisorySupport.isAdvisoryTopic(destination)) {
667                if (getConsumerCount(connectionId) >= connector.getMaximumConsumersAllowedPerConnection()){
668                    throw new IllegalStateException("Can't add consumer on connection " + connectionId + ": at maximum limit: " + connector.getMaximumConsumersAllowedPerConnection());
669                }
670            }
671
672            broker.addConsumer(cs.getContext(), info);
673            try {
674                ss.addConsumer(info);
675                addConsumerBrokerExchange(info.getConsumerId());
676            } catch (IllegalStateException e) {
677                broker.removeConsumer(cs.getContext(), info);
678            }
679
680        }
681        return null;
682    }
683
684    @Override
685    public Response processRemoveConsumer(ConsumerId id, long lastDeliveredSequenceId) throws Exception {
686        SessionId sessionId = id.getParentId();
687        ConnectionId connectionId = sessionId.getParentId();
688        TransportConnectionState cs = lookupConnectionState(connectionId);
689        if (cs == null) {
690            throw new IllegalStateException("Cannot remove a consumer from a connection that had not been registered: "
691                    + connectionId);
692        }
693        SessionState ss = cs.getSessionState(sessionId);
694        if (ss == null) {
695            throw new IllegalStateException("Cannot remove a consumer from a session that had not been registered: "
696                    + sessionId);
697        }
698        ConsumerState consumerState = ss.removeConsumer(id);
699        if (consumerState == null) {
700            throw new IllegalStateException("Cannot remove a consumer that had not been registered: " + id);
701        }
702        ConsumerInfo info = consumerState.getInfo();
703        info.setLastDeliveredSequenceId(lastDeliveredSequenceId);
704        broker.removeConsumer(cs.getContext(), consumerState.getInfo());
705        removeConsumerBrokerExchange(id);
706        return null;
707    }
708
709    @Override
710    public Response processAddSession(SessionInfo info) throws Exception {
711        ConnectionId connectionId = info.getSessionId().getParentId();
712        TransportConnectionState cs = lookupConnectionState(connectionId);
713        // Avoid replaying dup commands
714        if (cs != null && !cs.getSessionIds().contains(info.getSessionId())) {
715            broker.addSession(cs.getContext(), info);
716            try {
717                cs.addSession(info);
718            } catch (IllegalStateException e) {
719                LOG.warn("Failed to add session: {}", info.getSessionId(), e);
720                broker.removeSession(cs.getContext(), info);
721            }
722        }
723        return null;
724    }
725
726    @Override
727    public Response processRemoveSession(SessionId id, long lastDeliveredSequenceId) throws Exception {
728        ConnectionId connectionId = id.getParentId();
729        TransportConnectionState cs = lookupConnectionState(connectionId);
730        if (cs == null) {
731            throw new IllegalStateException("Cannot remove session from connection that had not been registered: " + connectionId);
732        }
733        SessionState session = cs.getSessionState(id);
734        if (session == null) {
735            throw new IllegalStateException("Cannot remove session that had not been registered: " + id);
736        }
737        // Don't let new consumers or producers get added while we are closing
738        // this down.
739        session.shutdown();
740        // Cascade the connection stop to the consumers and producers.
741        for (ConsumerId consumerId : session.getConsumerIds()) {
742            try {
743                processRemoveConsumer(consumerId, lastDeliveredSequenceId);
744            } catch (Throwable e) {
745                LOG.warn("Failed to remove consumer: {}", consumerId, e);
746            }
747        }
748        for (ProducerId producerId : session.getProducerIds()) {
749            try {
750                processRemoveProducer(producerId);
751            } catch (Throwable e) {
752                LOG.warn("Failed to remove producer: {}", producerId, e);
753            }
754        }
755        cs.removeSession(id);
756        broker.removeSession(cs.getContext(), session.getInfo());
757        return null;
758    }
759
760    @Override
761    public Response processAddConnection(ConnectionInfo info) throws Exception {
762        // Older clients should have been defaulting this field to true.. but
763        // they were not.
764        if (wireFormatInfo != null && wireFormatInfo.getVersion() <= 2) {
765            info.setClientMaster(true);
766        }
767        TransportConnectionState state;
768        // Make sure 2 concurrent connections by the same ID only generate 1
769        // TransportConnectionState object.
770        synchronized (brokerConnectionStates) {
771            state = (TransportConnectionState) brokerConnectionStates.get(info.getConnectionId());
772            if (state == null) {
773                state = new TransportConnectionState(info, this);
774                brokerConnectionStates.put(info.getConnectionId(), state);
775            }
776            state.incrementReference();
777        }
778        // If there are 2 concurrent connections for the same connection id,
779        // then last one in wins, we need to sync here
780        // to figure out the winner.
781        synchronized (state.getConnectionMutex()) {
782            if (state.getConnection() != this) {
783                LOG.debug("Killing previous stale connection: {}", state.getConnection().getRemoteAddress());
784                state.getConnection().stop();
785                LOG.debug("Connection {} taking over previous connection: {}", getRemoteAddress(), state.getConnection().getRemoteAddress());
786                state.setConnection(this);
787                state.reset(info);
788            }
789        }
790        registerConnectionState(info.getConnectionId(), state);
791        LOG.debug("Setting up new connection id: {}, address: {}, info: {}", new Object[]{ info.getConnectionId(), getRemoteAddress(), info });
792        this.faultTolerantConnection = info.isFaultTolerant();
793        // Setup the context.
794        String clientId = info.getClientId();
795        context = new ConnectionContext();
796        context.setBroker(broker);
797        context.setClientId(clientId);
798        context.setClientMaster(info.isClientMaster());
799        context.setConnection(this);
800        context.setConnectionId(info.getConnectionId());
801        context.setConnector(connector);
802        context.setMessageAuthorizationPolicy(getMessageAuthorizationPolicy());
803        context.setNetworkConnection(networkConnection);
804        context.setFaultTolerant(faultTolerantConnection);
805        context.setTransactions(new ConcurrentHashMap<TransactionId, Transaction>());
806        context.setUserName(info.getUserName());
807        context.setWireFormatInfo(wireFormatInfo);
808        context.setReconnect(info.isFailoverReconnect());
809        this.manageable = info.isManageable();
810        context.setConnectionState(state);
811        state.setContext(context);
812        state.setConnection(this);
813        if (info.getClientIp() == null) {
814            info.setClientIp(getRemoteAddress());
815        }
816
817        try {
818            broker.addConnection(context, info);
819        } catch (Exception e) {
820            synchronized (brokerConnectionStates) {
821                brokerConnectionStates.remove(info.getConnectionId());
822            }
823            unregisterConnectionState(info.getConnectionId());
824            LOG.warn("Failed to add Connection {} due to {}", info.getConnectionId(), e);
825            if (e instanceof SecurityException) {
826                // close this down - in case the peer of this transport doesn't play nice
827                delayedStop(2000, "Failed with SecurityException: " + e.getLocalizedMessage(), e);
828            }
829            throw e;
830        }
831        if (info.isManageable()) {
832            // send ConnectionCommand
833            ConnectionControl command = this.connector.getConnectionControl();
834            command.setFaultTolerant(broker.isFaultTolerantConfiguration());
835            if (info.isFailoverReconnect()) {
836                command.setRebalanceConnection(false);
837            }
838            dispatchAsync(command);
839        }
840        return null;
841    }
842
843    @Override
844    public synchronized Response processRemoveConnection(ConnectionId id, long lastDeliveredSequenceId)
845            throws InterruptedException {
846        LOG.debug("remove connection id: {}", id);
847        TransportConnectionState cs = lookupConnectionState(id);
848        if (cs != null) {
849            // Don't allow things to be added to the connection state while we
850            // are shutting down.
851            cs.shutdown();
852            // Cascade the connection stop to the sessions.
853            for (SessionId sessionId : cs.getSessionIds()) {
854                try {
855                    processRemoveSession(sessionId, lastDeliveredSequenceId);
856                } catch (Throwable e) {
857                    SERVICELOG.warn("Failed to remove session {}", sessionId, e);
858                }
859            }
860            // Cascade the connection stop to temp destinations.
861            for (Iterator<DestinationInfo> iter = cs.getTempDestinations().iterator(); iter.hasNext(); ) {
862                DestinationInfo di = iter.next();
863                try {
864                    broker.removeDestination(cs.getContext(), di.getDestination(), 0);
865                } catch (Throwable e) {
866                    SERVICELOG.warn("Failed to remove tmp destination {}", di.getDestination(), e);
867                }
868                iter.remove();
869            }
870            try {
871                broker.removeConnection(cs.getContext(), cs.getInfo(), transportException.get());
872            } catch (Throwable e) {
873                SERVICELOG.warn("Failed to remove connection {}", cs.getInfo(), e);
874            }
875            TransportConnectionState state = unregisterConnectionState(id);
876            if (state != null) {
877                synchronized (brokerConnectionStates) {
878                    // If we are the last reference, we should remove the state
879                    // from the broker.
880                    if (state.decrementReference() == 0) {
881                        brokerConnectionStates.remove(id);
882                    }
883                }
884            }
885        }
886        return null;
887    }
888
889    @Override
890    public Response processProducerAck(ProducerAck ack) throws Exception {
891        // A broker should not get ProducerAck messages.
892        return null;
893    }
894
895    @Override
896    public Connector getConnector() {
897        return connector;
898    }
899
900    @Override
901    public void dispatchSync(Command message) {
902        try {
903            processDispatch(message);
904        } catch (IOException e) {
905            serviceExceptionAsync(e);
906        }
907    }
908
909    @Override
910    public void dispatchAsync(Command message) {
911        if (!stopping.get()) {
912            if (taskRunner == null) {
913                dispatchSync(message);
914            } else {
915                synchronized (dispatchQueue) {
916                    dispatchQueue.add(message);
917                }
918                try {
919                    taskRunner.wakeup();
920                } catch (InterruptedException e) {
921                    Thread.currentThread().interrupt();
922                }
923            }
924        } else {
925            if (message.isMessageDispatch()) {
926                MessageDispatch md = (MessageDispatch) message;
927                TransmitCallback sub = md.getTransmitCallback();
928                broker.postProcessDispatch(md);
929                if (sub != null) {
930                    sub.onFailure();
931                }
932            }
933        }
934    }
935
936    protected void processDispatch(Command command) throws IOException {
937        MessageDispatch messageDispatch = (MessageDispatch) (command.isMessageDispatch() ? command : null);
938        try {
939            if (!stopping.get()) {
940                if (messageDispatch != null) {
941                    try {
942                        broker.preProcessDispatch(messageDispatch);
943                    } catch (RuntimeException convertToIO) {
944                        throw new IOException(convertToIO);
945                    }
946                }
947                dispatch(command);
948            }
949        } catch (IOException e) {
950            if (messageDispatch != null) {
951                TransmitCallback sub = messageDispatch.getTransmitCallback();
952                broker.postProcessDispatch(messageDispatch);
953                if (sub != null) {
954                    sub.onFailure();
955                }
956                messageDispatch = null;
957                throw e;
958            }
959        } finally {
960            if (messageDispatch != null) {
961                TransmitCallback sub = messageDispatch.getTransmitCallback();
962                broker.postProcessDispatch(messageDispatch);
963                if (sub != null) {
964                    sub.onSuccess();
965                }
966            }
967        }
968    }
969
970    @Override
971    public boolean iterate() {
972        try {
973            if (pendingStop || stopping.get()) {
974                if (dispatchStopped.compareAndSet(false, true)) {
975                    if (transportException.get() == null) {
976                        try {
977                            dispatch(new ShutdownInfo());
978                        } catch (Throwable ignore) {
979                        }
980                    }
981                    dispatchStoppedLatch.countDown();
982                }
983                return false;
984            }
985            if (!dispatchStopped.get()) {
986                Command command = null;
987                synchronized (dispatchQueue) {
988                    if (dispatchQueue.isEmpty()) {
989                        return false;
990                    }
991                    command = dispatchQueue.remove(0);
992                }
993                processDispatch(command);
994                return true;
995            }
996            return false;
997        } catch (IOException e) {
998            if (dispatchStopped.compareAndSet(false, true)) {
999                dispatchStoppedLatch.countDown();
1000            }
1001            serviceExceptionAsync(e);
1002            return false;
1003        }
1004    }
1005
1006    /**
1007     * Returns the statistics for this connection
1008     */
1009    @Override
1010    public ConnectionStatistics getStatistics() {
1011        return statistics;
1012    }
1013
1014    public MessageAuthorizationPolicy getMessageAuthorizationPolicy() {
1015        return messageAuthorizationPolicy;
1016    }
1017
1018    public void setMessageAuthorizationPolicy(MessageAuthorizationPolicy messageAuthorizationPolicy) {
1019        this.messageAuthorizationPolicy = messageAuthorizationPolicy;
1020    }
1021
1022    @Override
1023    public boolean isManageable() {
1024        return manageable;
1025    }
1026
1027    @Override
1028    public void start() throws Exception {
1029        try {
1030            synchronized (this) {
1031                starting = true;
1032                if (taskRunnerFactory != null) {
1033                    taskRunner = taskRunnerFactory.createTaskRunner(this, "ActiveMQ Connection Dispatcher: "
1034                            + getRemoteAddress());
1035                } else {
1036                    taskRunner = null;
1037                }
1038                transport.start();
1039                active = true;
1040                BrokerInfo info = connector.getBrokerInfo().copy();
1041                if (connector.isUpdateClusterClients()) {
1042                    info.setPeerBrokerInfos(this.broker.getPeerBrokerInfos());
1043                } else {
1044                    info.setPeerBrokerInfos(null);
1045                }
1046                dispatchAsync(info);
1047
1048                connector.onStarted(this);
1049            }
1050        } catch (Exception e) {
1051            // Force clean up on an error starting up.
1052            pendingStop = true;
1053            throw e;
1054        } finally {
1055            // stop() can be called from within the above block,
1056            // but we want to be sure start() completes before
1057            // stop() runs, so queue the stop until right now:
1058            setStarting(false);
1059            if (isPendingStop()) {
1060                LOG.debug("Calling the delayed stop() after start() {}", this);
1061                stop();
1062            }
1063        }
1064    }
1065
1066    @Override
1067    public void stop() throws Exception {
1068        // do not stop task the task runner factories (taskRunnerFactory, stopTaskRunnerFactory)
1069        // as their lifecycle is handled elsewhere
1070
1071        stopAsync();
1072        while (!stopped.await(5, TimeUnit.SECONDS)) {
1073            LOG.info("The connection to '{}' is taking a long time to shutdown.", transport.getRemoteAddress());
1074        }
1075    }
1076
1077    public void delayedStop(final int waitTime, final String reason, Throwable cause) {
1078        if (waitTime > 0) {
1079            synchronized (this) {
1080                pendingStop = true;
1081                transportException.set(cause);
1082            }
1083            try {
1084                stopTaskRunnerFactory.execute(new Runnable() {
1085                    @Override
1086                    public void run() {
1087                        try {
1088                            Thread.sleep(waitTime);
1089                            stopAsync();
1090                            LOG.info("Stopping {} because {}", transport.getRemoteAddress(), reason);
1091                        } catch (InterruptedException e) {
1092                        }
1093                    }
1094                });
1095            } catch (Throwable t) {
1096                LOG.warn("Cannot create stopAsync. This exception will be ignored.", t);
1097            }
1098        }
1099    }
1100
1101    public void stopAsync(Throwable cause) {
1102        transportException.set(cause);
1103        stopAsync();
1104    }
1105
1106    public void stopAsync() {
1107        // If we're in the middle of starting then go no further... for now.
1108        synchronized (this) {
1109            pendingStop = true;
1110            if (starting) {
1111                LOG.debug("stopAsync() called in the middle of start(). Delaying till start completes..");
1112                return;
1113            }
1114        }
1115        if (stopping.compareAndSet(false, true)) {
1116            // Let all the connection contexts know we are shutting down
1117            // so that in progress operations can notice and unblock.
1118            List<TransportConnectionState> connectionStates = listConnectionStates();
1119            for (TransportConnectionState cs : connectionStates) {
1120                ConnectionContext connectionContext = cs.getContext();
1121                if (connectionContext != null) {
1122                    connectionContext.getStopping().set(true);
1123                }
1124            }
1125            try {
1126                stopTaskRunnerFactory.execute(new Runnable() {
1127                    @Override
1128                    public void run() {
1129                        serviceLock.writeLock().lock();
1130                        try {
1131                            doStop();
1132                        } catch (Throwable e) {
1133                            LOG.debug("Error occurred while shutting down a connection {}", this, e);
1134                        } finally {
1135                            stopped.countDown();
1136                            serviceLock.writeLock().unlock();
1137                        }
1138                    }
1139                });
1140            } catch (Throwable t) {
1141                LOG.warn("Cannot create async transport stopper thread. This exception is ignored. Not waiting for stop to complete", t);
1142                stopped.countDown();
1143            }
1144        }
1145    }
1146
1147    @Override
1148    public String toString() {
1149        return "Transport Connection to: " + transport.getRemoteAddress();
1150    }
1151
1152    protected void doStop() throws Exception {
1153        LOG.debug("Stopping connection: {}", transport.getRemoteAddress());
1154        connector.onStopped(this);
1155        try {
1156            synchronized (this) {
1157                if (duplexBridge != null) {
1158                    duplexBridge.stop();
1159                }
1160            }
1161        } catch (Exception ignore) {
1162            LOG.trace("Exception caught stopping. This exception is ignored.", ignore);
1163        }
1164        try {
1165            transport.stop();
1166            LOG.debug("Stopped transport: {}", transport.getRemoteAddress());
1167        } catch (Exception e) {
1168            LOG.debug("Could not stop transport to {}. This exception is ignored.", transport.getRemoteAddress(), e);
1169        }
1170        if (taskRunner != null) {
1171            taskRunner.shutdown(1);
1172            taskRunner = null;
1173        }
1174        active = false;
1175        // Run the MessageDispatch callbacks so that message references get
1176        // cleaned up.
1177        synchronized (dispatchQueue) {
1178            for (Iterator<Command> iter = dispatchQueue.iterator(); iter.hasNext(); ) {
1179                Command command = iter.next();
1180                if (command.isMessageDispatch()) {
1181                    MessageDispatch md = (MessageDispatch) command;
1182                    TransmitCallback sub = md.getTransmitCallback();
1183                    broker.postProcessDispatch(md);
1184                    if (sub != null) {
1185                        sub.onFailure();
1186                    }
1187                }
1188            }
1189            dispatchQueue.clear();
1190        }
1191        //
1192        // Remove all logical connection associated with this connection
1193        // from the broker.
1194        if (!broker.isStopped()) {
1195            List<TransportConnectionState> connectionStates = listConnectionStates();
1196            connectionStates = listConnectionStates();
1197            for (TransportConnectionState cs : connectionStates) {
1198                cs.getContext().getStopping().set(true);
1199                try {
1200                    LOG.debug("Cleaning up connection resources: {}", getRemoteAddress());
1201                    processRemoveConnection(cs.getInfo().getConnectionId(), RemoveInfo.LAST_DELIVERED_UNKNOWN);
1202                } catch (Throwable ignore) {
1203                    LOG.debug("Exception caught removing connection {}. This exception is ignored.", cs.getInfo().getConnectionId(), ignore);
1204                }
1205            }
1206        }
1207        LOG.debug("Connection Stopped: {}", getRemoteAddress());
1208    }
1209
1210    /**
1211     * @return Returns the blockedCandidate.
1212     */
1213    public boolean isBlockedCandidate() {
1214        return blockedCandidate;
1215    }
1216
1217    /**
1218     * @param blockedCandidate The blockedCandidate to set.
1219     */
1220    public void setBlockedCandidate(boolean blockedCandidate) {
1221        this.blockedCandidate = blockedCandidate;
1222    }
1223
1224    /**
1225     * @return Returns the markedCandidate.
1226     */
1227    public boolean isMarkedCandidate() {
1228        return markedCandidate;
1229    }
1230
1231    /**
1232     * @param markedCandidate The markedCandidate to set.
1233     */
1234    public void setMarkedCandidate(boolean markedCandidate) {
1235        this.markedCandidate = markedCandidate;
1236        if (!markedCandidate) {
1237            timeStamp = 0;
1238            blockedCandidate = false;
1239        }
1240    }
1241
1242    /**
1243     * @param slow The slow to set.
1244     */
1245    public void setSlow(boolean slow) {
1246        this.slow = slow;
1247    }
1248
1249    /**
1250     * @return true if the Connection is slow
1251     */
1252    @Override
1253    public boolean isSlow() {
1254        return slow;
1255    }
1256
1257    /**
1258     * @return true if the Connection is potentially blocked
1259     */
1260    public boolean isMarkedBlockedCandidate() {
1261        return markedCandidate;
1262    }
1263
1264    /**
1265     * Mark the Connection, so we can deem if it's collectable on the next sweep
1266     */
1267    public void doMark() {
1268        if (timeStamp == 0) {
1269            timeStamp = System.currentTimeMillis();
1270        }
1271    }
1272
1273    /**
1274     * @return if after being marked, the Connection is still writing
1275     */
1276    @Override
1277    public boolean isBlocked() {
1278        return blocked;
1279    }
1280
1281    /**
1282     * @return true if the Connection is connected
1283     */
1284    @Override
1285    public boolean isConnected() {
1286        return connected;
1287    }
1288
1289    /**
1290     * @param blocked The blocked to set.
1291     */
1292    public void setBlocked(boolean blocked) {
1293        this.blocked = blocked;
1294    }
1295
1296    /**
1297     * @param connected The connected to set.
1298     */
1299    public void setConnected(boolean connected) {
1300        this.connected = connected;
1301    }
1302
1303    /**
1304     * @return true if the Connection is active
1305     */
1306    @Override
1307    public boolean isActive() {
1308        return active;
1309    }
1310
1311    /**
1312     * @param active The active to set.
1313     */
1314    public void setActive(boolean active) {
1315        this.active = active;
1316    }
1317
1318    /**
1319     * @return true if the Connection is starting
1320     */
1321    public synchronized boolean isStarting() {
1322        return starting;
1323    }
1324
1325    @Override
1326    public synchronized boolean isNetworkConnection() {
1327        return networkConnection;
1328    }
1329
1330    @Override
1331    public boolean isFaultTolerantConnection() {
1332        return this.faultTolerantConnection;
1333    }
1334
1335    protected synchronized void setStarting(boolean starting) {
1336        this.starting = starting;
1337    }
1338
1339    /**
1340     * @return true if the Connection needs to stop
1341     */
1342    public synchronized boolean isPendingStop() {
1343        return pendingStop;
1344    }
1345
1346    protected synchronized void setPendingStop(boolean pendingStop) {
1347        this.pendingStop = pendingStop;
1348    }
1349
1350    @Override
1351    public Response processBrokerInfo(BrokerInfo info) {
1352        if (info.isSlaveBroker()) {
1353            LOG.error(" Slave Brokers are no longer supported - slave trying to attach is: {}", info.getBrokerName());
1354        } else if (info.isNetworkConnection() && info.isDuplexConnection()) {
1355            // so this TransportConnection is the rear end of a network bridge
1356            // We have been requested to create a two way pipe ...
1357            try {
1358                Properties properties = MarshallingSupport.stringToProperties(info.getNetworkProperties());
1359                Map<String, String> props = createMap(properties);
1360                NetworkBridgeConfiguration config = new NetworkBridgeConfiguration();
1361                IntrospectionSupport.setProperties(config, props, "");
1362                config.setBrokerName(broker.getBrokerName());
1363
1364                // check for existing duplex connection hanging about
1365
1366                // We first look if existing network connection already exists for the same broker Id and network connector name
1367                // It's possible in case of brief network fault to have this transport connector side of the connection always active
1368                // and the duplex network connector side wanting to open a new one
1369                // In this case, the old connection must be broken
1370                String duplexNetworkConnectorId = config.getName() + "@" + info.getBrokerId();
1371                CopyOnWriteArrayList<TransportConnection> connections = this.connector.getConnections();
1372                synchronized (connections) {
1373                    for (Iterator<TransportConnection> iter = connections.iterator(); iter.hasNext(); ) {
1374                        TransportConnection c = iter.next();
1375                        if ((c != this) && (duplexNetworkConnectorId.equals(c.getDuplexNetworkConnectorId()))) {
1376                            LOG.warn("Stopping an existing active duplex connection [{}] for network connector ({}).", c, duplexNetworkConnectorId);
1377                            c.stopAsync();
1378                            // better to wait for a bit rather than get connection id already in use and failure to start new bridge
1379                            c.getStopped().await(1, TimeUnit.SECONDS);
1380                        }
1381                    }
1382                    setDuplexNetworkConnectorId(duplexNetworkConnectorId);
1383                }
1384                Transport localTransport = NetworkBridgeFactory.createLocalTransport(broker);
1385                Transport remoteBridgeTransport = transport;
1386                if (! (remoteBridgeTransport instanceof ResponseCorrelator)) {
1387                    // the vm transport case is already wrapped
1388                    remoteBridgeTransport = new ResponseCorrelator(remoteBridgeTransport);
1389                }
1390                String duplexName = localTransport.toString();
1391                if (duplexName.contains("#")) {
1392                    duplexName = duplexName.substring(duplexName.lastIndexOf("#"));
1393                }
1394                MBeanNetworkListener listener = new MBeanNetworkListener(broker.getBrokerService(), config, broker.getBrokerService().createDuplexNetworkConnectorObjectName(duplexName));
1395                listener.setCreatedByDuplex(true);
1396                duplexBridge = NetworkBridgeFactory.createBridge(config, localTransport, remoteBridgeTransport, listener);
1397                duplexBridge.setBrokerService(broker.getBrokerService());
1398                // now turn duplex off this side
1399                info.setDuplexConnection(false);
1400                duplexBridge.setCreatedByDuplex(true);
1401                duplexBridge.duplexStart(this, brokerInfo, info);
1402                LOG.info("Started responder end of duplex bridge {}", duplexNetworkConnectorId);
1403                return null;
1404            } catch (TransportDisposedIOException e) {
1405                LOG.warn("Duplex bridge {} was stopped before it was correctly started.", duplexNetworkConnectorId);
1406                return null;
1407            } catch (Exception e) {
1408                LOG.error("Failed to create responder end of duplex network bridge {}", duplexNetworkConnectorId, e);
1409                return null;
1410            }
1411        }
1412        // We only expect to get one broker info command per connection
1413        if (this.brokerInfo != null) {
1414            LOG.warn("Unexpected extra broker info command received: {}", info);
1415        }
1416        this.brokerInfo = info;
1417        networkConnection = true;
1418        List<TransportConnectionState> connectionStates = listConnectionStates();
1419        for (TransportConnectionState cs : connectionStates) {
1420            cs.getContext().setNetworkConnection(true);
1421        }
1422        return null;
1423    }
1424
1425    @SuppressWarnings({"unchecked", "rawtypes"})
1426    private HashMap<String, String> createMap(Properties properties) {
1427        return new HashMap(properties);
1428    }
1429
1430    protected void dispatch(Command command) throws IOException {
1431        try {
1432            setMarkedCandidate(true);
1433            transport.oneway(command);
1434        } finally {
1435            setMarkedCandidate(false);
1436        }
1437    }
1438
1439    @Override
1440    public String getRemoteAddress() {
1441        return transport.getRemoteAddress();
1442    }
1443
1444    public Transport getTransport() {
1445        return transport;
1446    }
1447
1448    @Override
1449    public String getConnectionId() {
1450        List<TransportConnectionState> connectionStates = listConnectionStates();
1451        for (TransportConnectionState cs : connectionStates) {
1452            if (cs.getInfo().getClientId() != null) {
1453                return cs.getInfo().getClientId();
1454            }
1455            return cs.getInfo().getConnectionId().toString();
1456        }
1457        return null;
1458    }
1459
1460    @Override
1461    public void updateClient(ConnectionControl control) {
1462        if (isActive() && isBlocked() == false && isFaultTolerantConnection() && this.wireFormatInfo != null
1463                && this.wireFormatInfo.getVersion() >= 6) {
1464            dispatchAsync(control);
1465        }
1466    }
1467
1468    public ProducerBrokerExchange getProducerBrokerExchangeIfExists(ProducerInfo producerInfo){
1469        ProducerBrokerExchange result = null;
1470        if (producerInfo != null && producerInfo.getProducerId() != null){
1471            synchronized (producerExchanges){
1472                result = producerExchanges.get(producerInfo.getProducerId());
1473            }
1474        }
1475        return result;
1476    }
1477
1478    private ProducerBrokerExchange getProducerBrokerExchange(ProducerId id) throws IOException {
1479        ProducerBrokerExchange result = producerExchanges.get(id);
1480        if (result == null) {
1481            synchronized (producerExchanges) {
1482                result = new ProducerBrokerExchange();
1483                TransportConnectionState state = lookupConnectionState(id);
1484                context = state.getContext();
1485                result.setConnectionContext(context);
1486                if (context.isReconnect() || (context.isNetworkConnection() && connector.isAuditNetworkProducers())) {
1487                    result.setLastStoredSequenceId(broker.getBrokerService().getPersistenceAdapter().getLastProducerSequenceId(id));
1488                }
1489                SessionState ss = state.getSessionState(id.getParentId());
1490                if (ss != null) {
1491                    result.setProducerState(ss.getProducerState(id));
1492                    ProducerState producerState = ss.getProducerState(id);
1493                    if (producerState != null && producerState.getInfo() != null) {
1494                        ProducerInfo info = producerState.getInfo();
1495                        result.setMutable(info.getDestination() == null || info.getDestination().isComposite());
1496                    }
1497                }
1498                producerExchanges.put(id, result);
1499            }
1500        } else {
1501            context = result.getConnectionContext();
1502        }
1503        return result;
1504    }
1505
1506    private void removeProducerBrokerExchange(ProducerId id) {
1507        synchronized (producerExchanges) {
1508            producerExchanges.remove(id);
1509        }
1510    }
1511
1512    private ConsumerBrokerExchange getConsumerBrokerExchange(ConsumerId id) {
1513        ConsumerBrokerExchange result = consumerExchanges.get(id);
1514        return result;
1515    }
1516
1517    private ConsumerBrokerExchange addConsumerBrokerExchange(ConsumerId id) {
1518        ConsumerBrokerExchange result = consumerExchanges.get(id);
1519        if (result == null) {
1520            synchronized (consumerExchanges) {
1521                result = new ConsumerBrokerExchange();
1522                TransportConnectionState state = lookupConnectionState(id);
1523                context = state.getContext();
1524                result.setConnectionContext(context);
1525                SessionState ss = state.getSessionState(id.getParentId());
1526                if (ss != null) {
1527                    ConsumerState cs = ss.getConsumerState(id);
1528                    if (cs != null) {
1529                        ConsumerInfo info = cs.getInfo();
1530                        if (info != null) {
1531                            if (info.getDestination() != null && info.getDestination().isPattern()) {
1532                                result.setWildcard(true);
1533                            }
1534                        }
1535                    }
1536                }
1537                consumerExchanges.put(id, result);
1538            }
1539        }
1540        return result;
1541    }
1542
1543    private void removeConsumerBrokerExchange(ConsumerId id) {
1544        synchronized (consumerExchanges) {
1545            consumerExchanges.remove(id);
1546        }
1547    }
1548
1549    public int getProtocolVersion() {
1550        return protocolVersion.get();
1551    }
1552
1553    @Override
1554    public Response processControlCommand(ControlCommand command) throws Exception {
1555        return null;
1556    }
1557
1558    @Override
1559    public Response processMessageDispatch(MessageDispatch dispatch) throws Exception {
1560        return null;
1561    }
1562
1563    @Override
1564    public Response processConnectionControl(ConnectionControl control) throws Exception {
1565        if (control != null) {
1566            faultTolerantConnection = control.isFaultTolerant();
1567        }
1568        return null;
1569    }
1570
1571    @Override
1572    public Response processConnectionError(ConnectionError error) throws Exception {
1573        return null;
1574    }
1575
1576    @Override
1577    public Response processConsumerControl(ConsumerControl control) throws Exception {
1578        ConsumerBrokerExchange consumerExchange = getConsumerBrokerExchange(control.getConsumerId());
1579        broker.processConsumerControl(consumerExchange, control);
1580        return null;
1581    }
1582
1583    protected synchronized TransportConnectionState registerConnectionState(ConnectionId connectionId,
1584                                                                            TransportConnectionState state) {
1585        TransportConnectionState cs = null;
1586        if (!connectionStateRegister.isEmpty() && !connectionStateRegister.doesHandleMultipleConnectionStates()) {
1587            // swap implementations
1588            TransportConnectionStateRegister newRegister = new MapTransportConnectionStateRegister();
1589            newRegister.intialize(connectionStateRegister);
1590            connectionStateRegister = newRegister;
1591        }
1592        cs = connectionStateRegister.registerConnectionState(connectionId, state);
1593        return cs;
1594    }
1595
1596    protected synchronized TransportConnectionState unregisterConnectionState(ConnectionId connectionId) {
1597        return connectionStateRegister.unregisterConnectionState(connectionId);
1598    }
1599
1600    protected synchronized List<TransportConnectionState> listConnectionStates() {
1601        return connectionStateRegister.listConnectionStates();
1602    }
1603
1604    protected synchronized TransportConnectionState lookupConnectionState(String connectionId) {
1605        return connectionStateRegister.lookupConnectionState(connectionId);
1606    }
1607
1608    protected synchronized TransportConnectionState lookupConnectionState(ConsumerId id) {
1609        return connectionStateRegister.lookupConnectionState(id);
1610    }
1611
1612    protected synchronized TransportConnectionState lookupConnectionState(ProducerId id) {
1613        return connectionStateRegister.lookupConnectionState(id);
1614    }
1615
1616    protected synchronized TransportConnectionState lookupConnectionState(SessionId id) {
1617        return connectionStateRegister.lookupConnectionState(id);
1618    }
1619
1620    // public only for testing
1621    public synchronized TransportConnectionState lookupConnectionState(ConnectionId connectionId) {
1622        return connectionStateRegister.lookupConnectionState(connectionId);
1623    }
1624
1625    protected synchronized void setDuplexNetworkConnectorId(String duplexNetworkConnectorId) {
1626        this.duplexNetworkConnectorId = duplexNetworkConnectorId;
1627    }
1628
1629    protected synchronized String getDuplexNetworkConnectorId() {
1630        return this.duplexNetworkConnectorId;
1631    }
1632
1633    public boolean isStopping() {
1634        return stopping.get();
1635    }
1636
1637    protected CountDownLatch getStopped() {
1638        return stopped;
1639    }
1640
1641    private int getProducerCount(ConnectionId connectionId) {
1642        int result = 0;
1643        TransportConnectionState cs = lookupConnectionState(connectionId);
1644        if (cs != null) {
1645            for (SessionId sessionId : cs.getSessionIds()) {
1646                SessionState sessionState = cs.getSessionState(sessionId);
1647                if (sessionState != null) {
1648                    result += sessionState.getProducerIds().size();
1649                }
1650            }
1651        }
1652        return result;
1653    }
1654
1655    private int getConsumerCount(ConnectionId connectionId) {
1656        int result = 0;
1657        TransportConnectionState cs = lookupConnectionState(connectionId);
1658        if (cs != null) {
1659            for (SessionId sessionId : cs.getSessionIds()) {
1660                SessionState sessionState = cs.getSessionState(sessionId);
1661                if (sessionState != null) {
1662                    result += sessionState.getConsumerIds().size();
1663                }
1664            }
1665        }
1666        return result;
1667    }
1668
1669    public WireFormatInfo getRemoteWireFormatInfo() {
1670        return wireFormatInfo;
1671    }
1672}