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