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.transport.amqp.protocol;
018
019import static org.apache.activemq.transport.amqp.AmqpSupport.ANONYMOUS_RELAY;
020import static org.apache.activemq.transport.amqp.AmqpSupport.CONNECTION_OPEN_FAILED;
021import static org.apache.activemq.transport.amqp.AmqpSupport.CONTAINER_ID;
022import static org.apache.activemq.transport.amqp.AmqpSupport.INVALID_FIELD;
023import static org.apache.activemq.transport.amqp.AmqpSupport.PLATFORM;
024import static org.apache.activemq.transport.amqp.AmqpSupport.PRODUCT;
025import static org.apache.activemq.transport.amqp.AmqpSupport.QUEUE_PREFIX;
026import static org.apache.activemq.transport.amqp.AmqpSupport.TEMP_QUEUE_CAPABILITY;
027import static org.apache.activemq.transport.amqp.AmqpSupport.TEMP_TOPIC_CAPABILITY;
028import static org.apache.activemq.transport.amqp.AmqpSupport.TOPIC_PREFIX;
029import static org.apache.activemq.transport.amqp.AmqpSupport.VERSION;
030import static org.apache.activemq.transport.amqp.AmqpSupport.contains;
031
032import java.io.BufferedReader;
033import java.io.IOException;
034import java.io.InputStream;
035import java.io.InputStreamReader;
036import java.nio.ByteBuffer;
037import java.util.Date;
038import java.util.HashMap;
039import java.util.Map;
040import java.util.concurrent.ConcurrentHashMap;
041import java.util.concurrent.ConcurrentMap;
042import java.util.concurrent.TimeUnit;
043import java.util.concurrent.atomic.AtomicInteger;
044
045import javax.jms.InvalidClientIDException;
046
047import org.apache.activemq.broker.BrokerService;
048import org.apache.activemq.broker.region.DurableTopicSubscription;
049import org.apache.activemq.broker.region.RegionBroker;
050import org.apache.activemq.broker.region.TopicRegion;
051import org.apache.activemq.command.ActiveMQDestination;
052import org.apache.activemq.command.ActiveMQTempDestination;
053import org.apache.activemq.command.ActiveMQTempQueue;
054import org.apache.activemq.command.ActiveMQTempTopic;
055import org.apache.activemq.command.Command;
056import org.apache.activemq.command.ConnectionError;
057import org.apache.activemq.command.ConnectionId;
058import org.apache.activemq.command.ConnectionInfo;
059import org.apache.activemq.command.ConsumerControl;
060import org.apache.activemq.command.ConsumerId;
061import org.apache.activemq.command.ConsumerInfo;
062import org.apache.activemq.command.DestinationInfo;
063import org.apache.activemq.command.ExceptionResponse;
064import org.apache.activemq.command.LocalTransactionId;
065import org.apache.activemq.command.MessageDispatch;
066import org.apache.activemq.command.RemoveInfo;
067import org.apache.activemq.command.Response;
068import org.apache.activemq.command.SessionId;
069import org.apache.activemq.command.ShutdownInfo;
070import org.apache.activemq.command.TransactionId;
071import org.apache.activemq.transport.InactivityIOException;
072import org.apache.activemq.transport.amqp.AmqpHeader;
073import org.apache.activemq.transport.amqp.AmqpInactivityMonitor;
074import org.apache.activemq.transport.amqp.AmqpProtocolConverter;
075import org.apache.activemq.transport.amqp.AmqpProtocolException;
076import org.apache.activemq.transport.amqp.AmqpTransport;
077import org.apache.activemq.transport.amqp.AmqpTransportFilter;
078import org.apache.activemq.transport.amqp.AmqpWireFormat;
079import org.apache.activemq.transport.amqp.ResponseHandler;
080import org.apache.activemq.transport.amqp.sasl.AmqpAuthenticator;
081import org.apache.activemq.util.IOExceptionSupport;
082import org.apache.activemq.util.IdGenerator;
083import org.apache.qpid.proton.Proton;
084import org.apache.qpid.proton.amqp.Symbol;
085import org.apache.qpid.proton.amqp.transaction.Coordinator;
086import org.apache.qpid.proton.amqp.transport.AmqpError;
087import org.apache.qpid.proton.amqp.transport.ErrorCondition;
088import org.apache.qpid.proton.engine.Collector;
089import org.apache.qpid.proton.engine.Connection;
090import org.apache.qpid.proton.engine.Delivery;
091import org.apache.qpid.proton.engine.EndpointState;
092import org.apache.qpid.proton.engine.Event;
093import org.apache.qpid.proton.engine.Link;
094import org.apache.qpid.proton.engine.Receiver;
095import org.apache.qpid.proton.engine.Sender;
096import org.apache.qpid.proton.engine.Session;
097import org.apache.qpid.proton.engine.Transport;
098import org.apache.qpid.proton.engine.impl.CollectorImpl;
099import org.apache.qpid.proton.engine.impl.ProtocolTracer;
100import org.apache.qpid.proton.engine.impl.TransportImpl;
101import org.apache.qpid.proton.framing.TransportFrame;
102import org.fusesource.hawtbuf.Buffer;
103import org.slf4j.Logger;
104import org.slf4j.LoggerFactory;
105
106/**
107 * Implements the mechanics of managing a single remote peer connection.
108 */
109public class AmqpConnection implements AmqpProtocolConverter {
110
111    private static final Logger TRACE_FRAMES = AmqpTransportFilter.TRACE_FRAMES;
112    private static final Logger LOG = LoggerFactory.getLogger(AmqpConnection.class);
113    private static final int CHANNEL_MAX = 32767;
114    private static final String BROKER_VERSION;
115    private static final String BROKER_PLATFORM;
116
117    static {
118        String javaVersion = System.getProperty("java.version");
119
120        BROKER_PLATFORM = "Java/" + (javaVersion == null ? "unknown" : javaVersion);
121
122        InputStream in = null;
123        String version = "5.12.0";
124        if ((in = AmqpConnection.class.getResourceAsStream("/org/apache/activemq/version.txt")) != null) {
125            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
126            try {
127                version = reader.readLine();
128            } catch(Exception e) {
129            }
130        }
131        BROKER_VERSION = version;
132    }
133
134    private final Transport protonTransport = Proton.transport();
135    private final Connection protonConnection = Proton.connection();
136    private final Collector eventCollector = new CollectorImpl();
137
138    private final AmqpTransport amqpTransport;
139    private final AmqpWireFormat amqpWireFormat;
140    private final BrokerService brokerService;
141
142    private static final IdGenerator CONNECTION_ID_GENERATOR = new IdGenerator();
143    private final AtomicInteger lastCommandId = new AtomicInteger();
144    private final ConnectionId connectionId = new ConnectionId(CONNECTION_ID_GENERATOR.generateId());
145    private final ConnectionInfo connectionInfo = new ConnectionInfo();
146    private long nextSessionId;
147    private long nextTempDestinationId;
148    private long nextTransactionId;
149    private boolean closing;
150    private boolean closedSocket;
151    private AmqpAuthenticator authenticator;
152
153    private final Map<TransactionId, AmqpTransactionCoordinator> transactions = new HashMap<TransactionId, AmqpTransactionCoordinator>();
154    private final ConcurrentMap<Integer, ResponseHandler> resposeHandlers = new ConcurrentHashMap<Integer, ResponseHandler>();
155    private final ConcurrentMap<ConsumerId, AmqpSender> subscriptionsByConsumerId = new ConcurrentHashMap<ConsumerId, AmqpSender>();
156
157    public AmqpConnection(AmqpTransport transport, BrokerService brokerService) {
158        this.amqpTransport = transport;
159
160        AmqpInactivityMonitor monitor = transport.getInactivityMonitor();
161        if (monitor != null) {
162            monitor.setAmqpTransport(amqpTransport);
163        }
164
165        this.amqpWireFormat = transport.getWireFormat();
166        this.brokerService = brokerService;
167
168        // the configured maxFrameSize on the URI.
169        int maxFrameSize = amqpWireFormat.getMaxAmqpFrameSize();
170        if (maxFrameSize > AmqpWireFormat.NO_AMQP_MAX_FRAME_SIZE) {
171            this.protonTransport.setMaxFrameSize(maxFrameSize);
172        }
173
174        this.protonTransport.bind(this.protonConnection);
175        this.protonTransport.setChannelMax(CHANNEL_MAX);
176
177        this.protonConnection.collect(eventCollector);
178
179        updateTracer();
180    }
181
182    /**
183     * Load and return a <code>[]Symbol</code> that contains the connection capabilities
184     * offered to new connections
185     *
186     * @return the capabilities that are offered to new clients on connect.
187     */
188    protected Symbol[] getConnectionCapabilitiesOffered() {
189        return new Symbol[]{ ANONYMOUS_RELAY };
190    }
191
192    /**
193     * Load and return a <code>Map<Symbol, Object></code> that contains the properties
194     * that this connection supplies to incoming connections.
195     *
196     * @return the properties that are offered to the incoming connection.
197     */
198    protected Map<Symbol, Object> getConnetionProperties() {
199        Map<Symbol, Object> properties = new HashMap<Symbol, Object>();
200
201        properties.put(QUEUE_PREFIX, "queue://");
202        properties.put(TOPIC_PREFIX, "topic://");
203        properties.put(PRODUCT, "ActiveMQ");
204        properties.put(VERSION, BROKER_VERSION);
205        properties.put(PLATFORM, BROKER_PLATFORM);
206
207        return properties;
208    }
209
210    /**
211     * Load and return a <code>Map<Symbol, Object></code> that contains the properties
212     * that this connection supplies to incoming connections when the open has failed
213     * and the remote should expect a close to follow.
214     *
215     * @return the properties that are offered to the incoming connection.
216     */
217    protected Map<Symbol, Object> getFailedConnetionProperties() {
218        Map<Symbol, Object> properties = new HashMap<Symbol, Object>();
219
220        properties.put(CONNECTION_OPEN_FAILED, true);
221
222        return properties;
223    }
224
225    @Override
226    public void updateTracer() {
227        if (amqpTransport.isTrace()) {
228            ((TransportImpl) protonTransport).setProtocolTracer(new ProtocolTracer() {
229                @Override
230                public void receivedFrame(TransportFrame transportFrame) {
231                    TRACE_FRAMES.trace("{} | RECV: {}", AmqpConnection.this.amqpTransport.getRemoteAddress(), transportFrame.getBody());
232                }
233
234                @Override
235                public void sentFrame(TransportFrame transportFrame) {
236                    TRACE_FRAMES.trace("{} | SENT: {}", AmqpConnection.this.amqpTransport.getRemoteAddress(), transportFrame.getBody());
237                }
238            });
239        }
240    }
241
242    @Override
243    public long keepAlive() throws IOException {
244        long rescheduleAt = 0l;
245
246        LOG.trace("Performing connection:{} keep-alive processing", amqpTransport.getRemoteAddress());
247
248        if (protonConnection.getLocalState() != EndpointState.CLOSED) {
249            // Using nano time since it is not related to the wall clock, which may change
250            long now = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
251            rescheduleAt = protonTransport.tick(now) - now;
252            pumpProtonToSocket();
253            if (protonTransport.isClosed()) {
254                rescheduleAt = 0;
255                LOG.debug("Transport closed after inactivity check.");
256                throw new InactivityIOException("Channel was inactive for to long");
257            }
258        }
259
260        LOG.trace("Connection:{} keep alive processing done, next update in {} milliseconds.",
261                  amqpTransport.getRemoteAddress(), rescheduleAt);
262
263        return rescheduleAt;
264    }
265
266    //----- Connection Properties Accessors ----------------------------------//
267
268    /**
269     * @return the amount of credit assigned to AMQP receiver links created from
270     *         sender links on the remote peer.
271     */
272    public int getConfiguredReceiverCredit() {
273        return amqpWireFormat.getProducerCredit();
274    }
275
276    /**
277     * @return the transformer type that was configured for this AMQP transport.
278     */
279    public String getConfiguredTransformer() {
280        return amqpWireFormat.getTransformer();
281    }
282
283    /**
284     * @return the ActiveMQ ConnectionId that identifies this AMQP Connection.
285     */
286    public ConnectionId getConnectionId() {
287        return connectionId;
288    }
289
290    /**
291     * @return the Client ID used to create the connection with ActiveMQ
292     */
293    public String getClientId() {
294        return connectionInfo.getClientId();
295    }
296
297    /**
298     * @return the configured max frame size allowed for incoming messages.
299     */
300    public long getMaxFrameSize() {
301        return amqpWireFormat.getMaxFrameSize();
302    }
303
304    //----- Proton Event handling and IO support -----------------------------//
305
306    void pumpProtonToSocket() {
307        try {
308            boolean done = false;
309            while (!done) {
310                ByteBuffer toWrite = protonTransport.getOutputBuffer();
311                if (toWrite != null && toWrite.hasRemaining()) {
312                    LOG.trace("Sending {} bytes out", toWrite.limit());
313                    amqpTransport.sendToAmqp(toWrite);
314                    protonTransport.outputConsumed();
315                } else {
316                    done = true;
317                }
318            }
319        } catch (IOException e) {
320            amqpTransport.onException(e);
321        }
322    }
323
324    @Override
325    public void onAMQPData(Object command) throws Exception {
326        Buffer frame;
327        if (command.getClass() == AmqpHeader.class) {
328            AmqpHeader header = (AmqpHeader) command;
329
330            if (amqpWireFormat.isHeaderValid(header)) {
331                LOG.trace("Connection from an AMQP v1.0 client initiated. {}", header);
332            } else {
333                LOG.warn("Connection attempt from non AMQP v1.0 client. {}", header);
334                AmqpHeader reply = amqpWireFormat.getMinimallySupportedHeader();
335                amqpTransport.sendToAmqp(reply.getBuffer());
336                handleException(new AmqpProtocolException(
337                    "Connection from client using unsupported AMQP attempted", true));
338            }
339
340            switch (header.getProtocolId()) {
341                case 0:
342                    authenticator = null;
343                    break; // nothing to do..
344                case 3: // Client will be using SASL for auth..
345                    authenticator = new AmqpAuthenticator(amqpTransport, protonTransport.sasl(), brokerService);
346                    break;
347                default:
348            }
349            frame = header.getBuffer();
350        } else {
351            frame = (Buffer) command;
352        }
353
354        if (protonTransport.isClosed()) {
355            LOG.debug("Ignoring incoming AMQP data, transport is closed.");
356            return;
357        }
358
359        while (frame.length > 0) {
360            try {
361                int count = protonTransport.input(frame.data, frame.offset, frame.length);
362                frame.moveHead(count);
363            } catch (Throwable e) {
364                handleException(new AmqpProtocolException("Could not decode AMQP frame: " + frame, true, e));
365                return;
366            }
367
368            if (authenticator != null) {
369                processSaslExchange();
370            } else {
371                processProtonEvents();
372            }
373        }
374    }
375
376    private void processSaslExchange() throws Exception {
377        authenticator.processSaslExchange(connectionInfo);
378        if (authenticator.isDone()) {
379            amqpTransport.getWireFormat().resetMagicRead();
380        }
381        pumpProtonToSocket();
382    }
383
384    private void processProtonEvents() throws Exception {
385        try {
386            Event event = null;
387            while ((event = eventCollector.peek()) != null) {
388                if (amqpTransport.isTrace()) {
389                    LOG.trace("Processing event: {}", event.getType());
390                }
391                switch (event.getType()) {
392                    case CONNECTION_REMOTE_OPEN:
393                        processConnectionOpen(event.getConnection());
394                        break;
395                    case CONNECTION_REMOTE_CLOSE:
396                        processConnectionClose(event.getConnection());
397                        break;
398                    case SESSION_REMOTE_OPEN:
399                        processSessionOpen(event.getSession());
400                        break;
401                    case SESSION_REMOTE_CLOSE:
402                        processSessionClose(event.getSession());
403                        break;
404                    case LINK_REMOTE_OPEN:
405                        processLinkOpen(event.getLink());
406                        break;
407                    case LINK_REMOTE_DETACH:
408                        processLinkDetach(event.getLink());
409                        break;
410                    case LINK_REMOTE_CLOSE:
411                        processLinkClose(event.getLink());
412                        break;
413                    case LINK_FLOW:
414                        processLinkFlow(event.getLink());
415                        break;
416                    case DELIVERY:
417                        processDelivery(event.getDelivery());
418                        break;
419                    default:
420                        break;
421                }
422
423                eventCollector.pop();
424            }
425
426        } catch (Throwable e) {
427            handleException(new AmqpProtocolException("Could not process AMQP commands", true, e));
428        }
429
430        pumpProtonToSocket();
431    }
432
433    protected void processConnectionOpen(Connection connection) throws Exception {
434
435        stopConnectionTimeoutChecker();
436
437        connectionInfo.setResponseRequired(true);
438        connectionInfo.setConnectionId(connectionId);
439
440        String clientId = protonConnection.getRemoteContainer();
441        if (clientId != null && !clientId.isEmpty()) {
442            connectionInfo.setClientId(clientId);
443        }
444
445        connectionInfo.setTransportContext(amqpTransport.getPeerCertificates());
446
447        if (connection.getTransport().getRemoteIdleTimeout() > 0 && !amqpTransport.isUseInactivityMonitor()) {
448            // We cannot meet the requested Idle processing because the inactivity monitor is
449            // disabled so we won't send idle frames to match the request.
450            protonConnection.setProperties(getFailedConnetionProperties());
451            protonConnection.open();
452            protonConnection.setCondition(new ErrorCondition(AmqpError.PRECONDITION_FAILED, "Cannot send idle frames"));
453            protonConnection.close();
454            pumpProtonToSocket();
455
456            amqpTransport.onException(new IOException(
457                "Connection failed, remote requested idle processing but inactivity monitoring is disbaled."));
458            return;
459        }
460
461        sendToActiveMQ(connectionInfo, new ResponseHandler() {
462            @Override
463            public void onResponse(AmqpProtocolConverter converter, Response response) throws IOException {
464                Throwable exception = null;
465                try {
466                    if (response.isException()) {
467                        protonConnection.setProperties(getFailedConnetionProperties());
468                        protonConnection.open();
469
470                        exception = ((ExceptionResponse) response).getException();
471                        if (exception instanceof SecurityException) {
472                            protonConnection.setCondition(new ErrorCondition(AmqpError.UNAUTHORIZED_ACCESS, exception.getMessage()));
473                        } else if (exception instanceof InvalidClientIDException) {
474                            ErrorCondition condition = new ErrorCondition(AmqpError.INVALID_FIELD, exception.getMessage());
475
476                            Map<Symbol, Object> infoMap = new HashMap<Symbol, Object> ();
477                            infoMap.put(INVALID_FIELD, CONTAINER_ID);
478                            condition.setInfo(infoMap);
479
480                            protonConnection.setCondition(condition);
481                        } else {
482                            protonConnection.setCondition(new ErrorCondition(AmqpError.ILLEGAL_STATE, exception.getMessage()));
483                        }
484
485                        protonConnection.close();
486                    } else {
487
488                        if (amqpTransport.isUseInactivityMonitor() && amqpWireFormat.getIdleTimeout() > 0) {
489                            LOG.trace("Connection requesting Idle timeout of: {} mills", amqpWireFormat.getIdleTimeout());
490                            protonTransport.setIdleTimeout(amqpWireFormat.getIdleTimeout());
491                        }
492
493                        protonConnection.setOfferedCapabilities(getConnectionCapabilitiesOffered());
494                        protonConnection.setProperties(getConnetionProperties());
495                        protonConnection.open();
496
497                        configureInactivityMonitor();
498                    }
499                } finally {
500                    pumpProtonToSocket();
501
502                    if (response.isException()) {
503                        amqpTransport.onException(IOExceptionSupport.create(exception));
504                    }
505                }
506            }
507        });
508    }
509
510    protected void processConnectionClose(Connection connection) throws Exception {
511        if (!closing) {
512            closing = true;
513            sendToActiveMQ(new RemoveInfo(connectionId), new ResponseHandler() {
514                @Override
515                public void onResponse(AmqpProtocolConverter converter, Response response) throws IOException {
516                    protonConnection.close();
517                    protonConnection.free();
518
519                    if (!closedSocket) {
520                        pumpProtonToSocket();
521                    }
522                }
523            });
524
525            sendToActiveMQ(new ShutdownInfo());
526        }
527    }
528
529    protected void processSessionOpen(Session protonSession) throws Exception {
530        new AmqpSession(this, getNextSessionId(), protonSession).open();
531    }
532
533    protected void processSessionClose(Session protonSession) throws Exception {
534        if (protonSession.getContext() != null) {
535            ((AmqpResource) protonSession.getContext()).close();
536        } else {
537            protonSession.close();
538            protonSession.free();
539        }
540    }
541
542    protected void processLinkOpen(Link link) throws Exception {
543        link.setSource(link.getRemoteSource());
544        link.setTarget(link.getRemoteTarget());
545
546        AmqpSession session = (AmqpSession) link.getSession().getContext();
547        if (link instanceof Receiver) {
548            if (link.getRemoteTarget() instanceof Coordinator) {
549                session.createCoordinator((Receiver) link);
550            } else {
551                session.createReceiver((Receiver) link);
552            }
553        } else {
554            session.createSender((Sender) link);
555        }
556    }
557
558    protected void processLinkDetach(Link link) throws Exception {
559        Object context = link.getContext();
560
561        if (context instanceof AmqpLink) {
562            ((AmqpLink) context).detach();
563        } else {
564            link.detach();
565            link.free();
566        }
567    }
568
569    protected void processLinkClose(Link link) throws Exception {
570        Object context = link.getContext();
571
572        if (context instanceof AmqpLink) {
573            ((AmqpLink) context).close();;
574        } else {
575            link.close();
576            link.free();
577        }
578    }
579
580    protected void processLinkFlow(Link link) throws Exception {
581        Object context = link.getContext();
582        if (context instanceof AmqpLink) {
583            ((AmqpLink) context).flow();
584        }
585    }
586
587    protected void processDelivery(Delivery delivery) throws Exception {
588        if (!delivery.isPartial()) {
589            Object context = delivery.getLink().getContext();
590            if (context instanceof AmqpLink) {
591                AmqpLink amqpLink = (AmqpLink) context;
592                amqpLink.delivery(delivery);
593            }
594        }
595    }
596
597    //----- Event entry points for ActiveMQ commands and errors --------------//
598
599    @Override
600    public void onAMQPException(IOException error) {
601        closedSocket = true;
602        if (!closing) {
603            try {
604                closing = true;
605                // Attempt to inform the other end that we are going to close
606                // so that the client doesn't wait around forever.
607                protonConnection.setCondition(new ErrorCondition(AmqpError.DECODE_ERROR, error.getMessage()));
608                protonConnection.close();
609                pumpProtonToSocket();
610            } catch (Exception ignore) {
611            }
612            amqpTransport.sendToActiveMQ(error);
613        } else {
614            try {
615                amqpTransport.stop();
616            } catch (Exception ignore) {
617            }
618        }
619    }
620
621    @Override
622    public void onActiveMQCommand(Command command) throws Exception {
623        if (command.isResponse()) {
624            Response response = (Response) command;
625            ResponseHandler rh = resposeHandlers.remove(Integer.valueOf(response.getCorrelationId()));
626            if (rh != null) {
627                rh.onResponse(this, response);
628            } else {
629                // Pass down any unexpected errors. Should this close the connection?
630                if (response.isException()) {
631                    Throwable exception = ((ExceptionResponse) response).getException();
632                    handleException(exception);
633                }
634            }
635        } else if (command.isMessageDispatch()) {
636            MessageDispatch dispatch = (MessageDispatch) command;
637            AmqpSender sender = subscriptionsByConsumerId.get(dispatch.getConsumerId());
638            if (sender != null) {
639                // End of Queue Browse will have no Message object.
640                if (dispatch.getMessage() != null) {
641                    LOG.trace("Dispatching MessageId: {} to consumer", dispatch.getMessage().getMessageId());
642                } else {
643                    LOG.trace("Dispatching End of Browse Command to consumer {}", dispatch.getConsumerId());
644                }
645                sender.onMessageDispatch(dispatch);
646                if (dispatch.getMessage() != null) {
647                    LOG.trace("Finished Dispatch of MessageId: {} to consumer", dispatch.getMessage().getMessageId());
648                }
649            }
650        } else if (command.getDataStructureType() == ConnectionError.DATA_STRUCTURE_TYPE) {
651            // Pass down any unexpected async errors. Should this close the connection?
652            Throwable exception = ((ConnectionError) command).getException();
653            handleException(exception);
654        } else if (command.isConsumerControl()) {
655            ConsumerControl control = (ConsumerControl) command;
656            AmqpSender sender = subscriptionsByConsumerId.get(control.getConsumerId());
657            if (sender != null) {
658                sender.onConsumerControl(control);
659            }
660        } else if (command.isBrokerInfo()) {
661            // ignore
662        } else {
663            LOG.debug("Do not know how to process ActiveMQ Command {}", command);
664        }
665    }
666
667    //----- Utility methods for connection resources to use ------------------//
668
669    void registerSender(ConsumerId consumerId, AmqpSender sender) {
670        subscriptionsByConsumerId.put(consumerId, sender);
671    }
672
673    void unregisterSender(ConsumerId consumerId) {
674        subscriptionsByConsumerId.remove(consumerId);
675    }
676
677    void registerTransaction(TransactionId txId, AmqpTransactionCoordinator coordinator) {
678        transactions.put(txId, coordinator);
679    }
680
681    void unregisterTransaction(TransactionId txId) {
682        transactions.remove(txId);
683    }
684
685    AmqpTransactionCoordinator getTxCoordinator(TransactionId txId) {
686        return transactions.get(txId);
687    }
688
689    LocalTransactionId getNextTransactionId() {
690        return new LocalTransactionId(getConnectionId(), ++nextTransactionId);
691    }
692
693    ConsumerInfo lookupSubscription(String subscriptionName) throws AmqpProtocolException {
694        ConsumerInfo result = null;
695        RegionBroker regionBroker;
696
697        try {
698            regionBroker = (RegionBroker) brokerService.getBroker().getAdaptor(RegionBroker.class);
699        } catch (Exception e) {
700            throw new AmqpProtocolException("Error finding subscription: " + subscriptionName + ": " + e.getMessage(), false, e);
701        }
702
703        final TopicRegion topicRegion = (TopicRegion) regionBroker.getTopicRegion();
704        DurableTopicSubscription subscription = topicRegion.lookupSubscription(subscriptionName, connectionInfo.getClientId());
705        if (subscription != null) {
706            result = subscription.getConsumerInfo();
707        }
708
709        return result;
710    }
711
712    ActiveMQDestination createTemporaryDestination(final Link link, Symbol[] capabilities) {
713        ActiveMQDestination rc = null;
714        if (contains(capabilities, TEMP_TOPIC_CAPABILITY)) {
715            rc = new ActiveMQTempTopic(connectionId, nextTempDestinationId++);
716        } else if (contains(capabilities, TEMP_QUEUE_CAPABILITY)) {
717            rc = new ActiveMQTempQueue(connectionId, nextTempDestinationId++);
718        } else {
719            LOG.debug("Dynamic link request with no type capability, defaults to Temporary Queue");
720            rc = new ActiveMQTempQueue(connectionId, nextTempDestinationId++);
721        }
722
723        DestinationInfo info = new DestinationInfo();
724        info.setConnectionId(connectionId);
725        info.setOperationType(DestinationInfo.ADD_OPERATION_TYPE);
726        info.setDestination(rc);
727
728        sendToActiveMQ(info, new ResponseHandler() {
729
730            @Override
731            public void onResponse(AmqpProtocolConverter converter, Response response) throws IOException {
732                if (response.isException()) {
733                    link.setSource(null);
734
735                    Throwable exception = ((ExceptionResponse) response).getException();
736                    if (exception instanceof SecurityException) {
737                        link.setCondition(new ErrorCondition(AmqpError.UNAUTHORIZED_ACCESS, exception.getMessage()));
738                    } else {
739                        link.setCondition(new ErrorCondition(AmqpError.INTERNAL_ERROR, exception.getMessage()));
740                    }
741
742                    link.close();
743                    link.free();
744                }
745            }
746        });
747
748        return rc;
749    }
750
751    void deleteTemporaryDestination(ActiveMQTempDestination destination) {
752        DestinationInfo info = new DestinationInfo();
753        info.setConnectionId(connectionId);
754        info.setOperationType(DestinationInfo.REMOVE_OPERATION_TYPE);
755        info.setDestination(destination);
756
757        sendToActiveMQ(info, new ResponseHandler() {
758
759            @Override
760            public void onResponse(AmqpProtocolConverter converter, Response response) throws IOException {
761                if (response.isException()) {
762                    Throwable exception = ((ExceptionResponse) response).getException();
763                    LOG.debug("Error during temp destination removeal: {}", exception.getMessage());
764                }
765            }
766        });
767    }
768
769    void sendToActiveMQ(Command command) {
770        sendToActiveMQ(command, null);
771    }
772
773    void sendToActiveMQ(Command command, ResponseHandler handler) {
774        command.setCommandId(lastCommandId.incrementAndGet());
775        if (handler != null) {
776            command.setResponseRequired(true);
777            resposeHandlers.put(Integer.valueOf(command.getCommandId()), handler);
778        }
779        amqpTransport.sendToActiveMQ(command);
780    }
781
782    void handleException(Throwable exception) {
783        LOG.debug("Exception detail", exception);
784        if (exception instanceof AmqpProtocolException) {
785            onAMQPException((IOException) exception);
786        } else {
787            try {
788                // Must ensure that the broker removes Connection resources.
789                sendToActiveMQ(new ShutdownInfo());
790                amqpTransport.stop();
791            } catch (Throwable e) {
792                LOG.error("Failed to stop AMQP Transport ", e);
793            }
794        }
795    }
796
797    //----- Internal implementation ------------------------------------------//
798
799    private SessionId getNextSessionId() {
800        return new SessionId(connectionId, nextSessionId++);
801    }
802
803    private void stopConnectionTimeoutChecker() {
804        AmqpInactivityMonitor monitor = amqpTransport.getInactivityMonitor();
805        if (monitor != null) {
806            monitor.stopConnectionTimeoutChecker();
807        }
808    }
809
810    private void configureInactivityMonitor() {
811        AmqpInactivityMonitor monitor = amqpTransport.getInactivityMonitor();
812        if (monitor == null) {
813            return;
814        }
815
816        // If either end has idle timeout requirements then the tick method
817        // will give us a deadline on the next time we need to tick() in order
818        // to meet those obligations.
819        // Using nano time since it is not related to the wall clock, which may change
820        long now = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
821        long nextIdleCheck = protonTransport.tick(now);
822        if (nextIdleCheck > 0) {
823            long delay = nextIdleCheck - now;
824            LOG.trace("Connection keep-alive processing starts in: {}", delay);
825            monitor.startKeepAliveTask(delay);
826        } else {
827            LOG.trace("Connection does not require keep-alive processing");
828        }
829    }
830}