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}