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}