/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.transport.amqp.protocol;
import static org.apache.activemq.transport.amqp.AmqpSupport.ANONYMOUS_RELAY;
import static org.apache.activemq.transport.amqp.AmqpSupport.CONNECTION_OPEN_FAILED;
import static org.apache.activemq.transport.amqp.AmqpSupport.CONTAINER_ID;
import static org.apache.activemq.transport.amqp.AmqpSupport.DELAYED_DELIVERY;
import static org.apache.activemq.transport.amqp.AmqpSupport.INVALID_FIELD;
import static org.apache.activemq.transport.amqp.AmqpSupport.PLATFORM;
import static org.apache.activemq.transport.amqp.AmqpSupport.PRODUCT;
import static org.apache.activemq.transport.amqp.AmqpSupport.QUEUE_PREFIX;
import static org.apache.activemq.transport.amqp.AmqpSupport.TEMP_QUEUE_CAPABILITY;
import static org.apache.activemq.transport.amqp.AmqpSupport.TEMP_TOPIC_CAPABILITY;
import static org.apache.activemq.transport.amqp.AmqpSupport.TOPIC_PREFIX;
import static org.apache.activemq.transport.amqp.AmqpSupport.VERSION;
import static org.apache.activemq.transport.amqp.AmqpSupport.contains;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.jms.InvalidClientIDException;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.broker.region.AbstractRegion;
import org.apache.activemq.broker.region.DurableTopicSubscription;
import org.apache.activemq.broker.region.RegionBroker;
import org.apache.activemq.broker.region.Subscription;
import org.apache.activemq.broker.region.TopicRegion;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.ActiveMQTempDestination;
import org.apache.activemq.command.ActiveMQTempQueue;
import org.apache.activemq.command.ActiveMQTempTopic;
import org.apache.activemq.command.Command;
import org.apache.activemq.command.ConnectionError;
import org.apache.activemq.command.ConnectionId;
import org.apache.activemq.command.ConnectionInfo;
import org.apache.activemq.command.ConsumerControl;
import org.apache.activemq.command.ConsumerId;
import org.apache.activemq.command.ConsumerInfo;
import org.apache.activemq.command.DestinationInfo;
import org.apache.activemq.command.ExceptionResponse;
import org.apache.activemq.command.LocalTransactionId;
import org.apache.activemq.command.MessageDispatch;
import org.apache.activemq.command.RemoveInfo;
import org.apache.activemq.command.Response;
import org.apache.activemq.command.SessionId;
import org.apache.activemq.command.ShutdownInfo;
import org.apache.activemq.command.TransactionId;
import org.apache.activemq.transport.InactivityIOException;
import org.apache.activemq.transport.amqp.AmqpHeader;
import org.apache.activemq.transport.amqp.AmqpInactivityMonitor;
import org.apache.activemq.transport.amqp.AmqpProtocolConverter;
import org.apache.activemq.transport.amqp.AmqpProtocolException;
import org.apache.activemq.transport.amqp.AmqpTransport;
import org.apache.activemq.transport.amqp.AmqpTransportFilter;
import org.apache.activemq.transport.amqp.AmqpWireFormat;
import org.apache.activemq.transport.amqp.ResponseHandler;
import org.apache.activemq.transport.amqp.sasl.AmqpAuthenticator;
import org.apache.activemq.util.IOExceptionSupport;
import org.apache.activemq.util.IdGenerator;
import org.apache.qpid.proton.Proton;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.transaction.Coordinator;
import org.apache.qpid.proton.amqp.transport.AmqpError;
import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.apache.qpid.proton.engine.Collector;
import org.apache.qpid.proton.engine.Connection;
import org.apache.qpid.proton.engine.Delivery;
import org.apache.qpid.proton.engine.EndpointState;
import org.apache.qpid.proton.engine.Event;
import org.apache.qpid.proton.engine.Link;
import org.apache.qpid.proton.engine.Receiver;
import org.apache.qpid.proton.engine.Sender;
import org.apache.qpid.proton.engine.Session;
import org.apache.qpid.proton.engine.Transport;
import org.apache.qpid.proton.engine.impl.CollectorImpl;
import org.apache.qpid.proton.engine.impl.ProtocolTracer;
import org.apache.qpid.proton.engine.impl.TransportImpl;
import org.apache.qpid.proton.framing.TransportFrame;
import org.fusesource.hawtbuf.Buffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implements the mechanics of managing a single remote peer connection.
*/
public class AmqpConnection implements AmqpProtocolConverter {
private static final Logger TRACE_FRAMES = AmqpTransportFilter.TRACE_FRAMES;
private static final Logger LOG = LoggerFactory.getLogger(AmqpConnection.class);
private static final int CHANNEL_MAX = 32767;
private static final String BROKER_VERSION;
private static final String BROKER_PLATFORM;
static {
String javaVersion = System.getProperty("java.version");
BROKER_PLATFORM = "Java/" + (javaVersion == null ? "unknown" : javaVersion);
InputStream in = null;
String version = "<unknown-5.x>";
if ((in = AmqpConnection.class.getResourceAsStream("/org/apache/activemq/version.txt")) != null) {
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
try {
version = reader.readLine();
} catch(Exception e) {
}
}
BROKER_VERSION = version;
}
private final Transport protonTransport = Proton.transport();
private final Connection protonConnection = Proton.connection();
private final Collector eventCollector = new CollectorImpl();
private final AmqpTransport amqpTransport;
private final AmqpWireFormat amqpWireFormat;
private final BrokerService brokerService;
private static final IdGenerator CONNECTION_ID_GENERATOR = new IdGenerator();
private final AtomicInteger lastCommandId = new AtomicInteger();
private final ConnectionId connectionId = new ConnectionId(CONNECTION_ID_GENERATOR.generateId());
private final ConnectionInfo connectionInfo = new ConnectionInfo();
private long nextSessionId;
private long nextTempDestinationId;
private long nextTransactionId;
private boolean closing;
private boolean closedSocket;
private AmqpAuthenticator authenticator;
private final Map<TransactionId, AmqpTransactionCoordinator> transactions = new HashMap<>();
private final ConcurrentMap<Integer, ResponseHandler> resposeHandlers = new ConcurrentHashMap<>();
private final ConcurrentMap<ConsumerId, AmqpSender> subscriptionsByConsumerId = new ConcurrentHashMap<>();
public AmqpConnection(AmqpTransport transport, BrokerService brokerService) {
this.amqpTransport = transport;
AmqpInactivityMonitor monitor = transport.getInactivityMonitor();
if (monitor != null) {
monitor.setAmqpTransport(amqpTransport);
}
this.amqpWireFormat = transport.getWireFormat();
this.brokerService = brokerService;
// the configured maxFrameSize on the URI.
int maxFrameSize = amqpWireFormat.getMaxAmqpFrameSize();
if (maxFrameSize > AmqpWireFormat.NO_AMQP_MAX_FRAME_SIZE) {
this.protonTransport.setMaxFrameSize(maxFrameSize);
}
this.protonTransport.bind(this.protonConnection);
this.protonTransport.setChannelMax(CHANNEL_MAX);
this.protonTransport.setEmitFlowEventOnSend(false);
this.protonConnection.collect(eventCollector);
updateTracer();
}
/**
* Load and return a <code>[]Symbol</code> that contains the connection capabilities
* offered to new connections
*
* @return the capabilities that are offered to new clients on connect.
*/
protected Symbol[] getConnectionCapabilitiesOffered() {
return new Symbol[]{ ANONYMOUS_RELAY, DELAYED_DELIVERY };
}
/**
* Load and return a <code>Map<Symbol, Object></code> that contains the properties
* that this connection supplies to incoming connections.
*
* @return the properties that are offered to the incoming connection.
*/
protected Map<Symbol, Object> getConnetionProperties() {
Map<Symbol, Object> properties = new HashMap<>();
properties.put(QUEUE_PREFIX, "queue://");
properties.put(TOPIC_PREFIX, "topic://");
properties.put(PRODUCT, "ActiveMQ");
properties.put(VERSION, BROKER_VERSION);
properties.put(PLATFORM, BROKER_PLATFORM);
return properties;
}
/**
* Load and return a <code>Map<Symbol, Object></code> that contains the properties
* that this connection supplies to incoming connections when the open has failed
* and the remote should expect a close to follow.
*
* @return the properties that are offered to the incoming connection.
*/
protected Map<Symbol, Object> getFailedConnetionProperties() {
Map<Symbol, Object> properties = new HashMap<>();
properties.put(CONNECTION_OPEN_FAILED, true);
return properties;
}
@Override
public void updateTracer() {
if (amqpTransport.isTrace()) {
((TransportImpl) protonTransport).setProtocolTracer(new ProtocolTracer() {
@Override
public void receivedFrame(TransportFrame transportFrame) {
TRACE_FRAMES.trace("{} | RECV: {}", AmqpConnection.this.amqpTransport.getRemoteAddress(), transportFrame.getBody());
}
@Override
public void sentFrame(TransportFrame transportFrame) {
TRACE_FRAMES.trace("{} | SENT: {}", AmqpConnection.this.amqpTransport.getRemoteAddress(), transportFrame.getBody());
}
});
}
}
@Override
public long keepAlive() throws IOException {
long rescheduleAt = 0l;
LOG.trace("Performing connection:{} keep-alive processing", amqpTransport.getRemoteAddress());
if (protonConnection.getLocalState() != EndpointState.CLOSED) {
// Using nano time since it is not related to the wall clock, which may change
long now = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
rescheduleAt = protonTransport.tick(now) - now;
pumpProtonToSocket();
if (protonTransport.isClosed()) {
rescheduleAt = 0;
LOG.debug("Transport closed after inactivity check.");
throw new InactivityIOException("Channel was inactive for to long");
}
}
LOG.trace("Connection:{} keep alive processing done, next update in {} milliseconds.",
amqpTransport.getRemoteAddress(), rescheduleAt);
return rescheduleAt;
}
//----- Connection Properties Accessors ----------------------------------//
/**
* @return the amount of credit assigned to AMQP receiver links created from
* sender links on the remote peer.
*/
public int getConfiguredReceiverCredit() {
return amqpWireFormat.getProducerCredit();
}
/**
* @return the transformer type that was configured for this AMQP transport.
*/
public String getConfiguredTransformer() {
return amqpWireFormat.getTransformer();
}
/**
* @return the ActiveMQ ConnectionId that identifies this AMQP Connection.
*/
public ConnectionId getConnectionId() {
return connectionId;
}
/**
* @return the Client ID used to create the connection with ActiveMQ
*/
public String getClientId() {
return connectionInfo.getClientId();
}
/**
* @return the configured max frame size allowed for incoming messages.
*/
public long getMaxFrameSize() {
return amqpWireFormat.getMaxFrameSize();
}
//----- Proton Event handling and IO support -----------------------------//
void pumpProtonToSocket() {
try {
boolean done = false;
while (!done) {
ByteBuffer toWrite = protonTransport.getOutputBuffer();
if (toWrite != null && toWrite.hasRemaining()) {
LOG.trace("Server: Sending {} bytes out", toWrite.limit());
amqpTransport.sendToAmqp(toWrite);
protonTransport.outputConsumed();
} else {
done = true;
}
}
} catch (IOException e) {
amqpTransport.onException(e);
}
}
@Override
public void onAMQPData(Object command) throws Exception {
Buffer frame;
if (command.getClass() == AmqpHeader.class) {
AmqpHeader header = (AmqpHeader) command;
if (amqpWireFormat.isHeaderValid(header, authenticator != null)) {
LOG.trace("Connection from an AMQP v1.0 client initiated. {}", header);
} else {
LOG.warn("Connection attempt from non AMQP v1.0 client. {}", header);
AmqpHeader reply = amqpWireFormat.getMinimallySupportedHeader();
amqpTransport.sendToAmqp(reply.getBuffer());
handleException(new AmqpProtocolException(
"Connection from client using unsupported AMQP attempted", true));
}
switch (header.getProtocolId()) {
case 0:
authenticator = null;
break; // nothing to do..
case 3: // Client will be using SASL for auth..
authenticator = new AmqpAuthenticator(amqpTransport, protonTransport.sasl(), brokerService);
break;
default:
}
frame = header.getBuffer();
} else {
frame = (Buffer) command;
}
if (protonTransport.isClosed()) {
LOG.debug("Ignoring incoming AMQP data, transport is closed.");
return;
}
LOG.trace("Server: Received from client: {} bytes", frame.getLength());
while (frame.length > 0) {
try {
int count = protonTransport.input(frame.data, frame.offset, frame.length);
frame.moveHead(count);
} catch (Throwable e) {
handleException(new AmqpProtocolException("Could not decode AMQP frame: " + frame, true, e));
return;
}
if (authenticator != null) {
processSaslExchange();
} else {
processProtonEvents();
}
}
}
private void processSaslExchange() throws Exception {
authenticator.processSaslExchange(connectionInfo);
if (authenticator.isDone()) {
amqpTransport.getWireFormat().resetMagicRead();
}
pumpProtonToSocket();
}
private void processProtonEvents() throws Exception {
try {
Event event = null;
while ((event = eventCollector.peek()) != null) {
if (amqpTransport.isTrace()) {
LOG.trace("Server: Processing event: {}", event.getType());
}
switch (event.getType()) {
case CONNECTION_REMOTE_OPEN:
processConnectionOpen(event.getConnection());
break;
case CONNECTION_REMOTE_CLOSE:
processConnectionClose(event.getConnection());
break;
case SESSION_REMOTE_OPEN:
processSessionOpen(event.getSession());
break;
case SESSION_REMOTE_CLOSE:
processSessionClose(event.getSession());
break;
case LINK_REMOTE_OPEN:
processLinkOpen(event.getLink());
break;
case LINK_REMOTE_DETACH:
processLinkDetach(event.getLink());
break;
case LINK_REMOTE_CLOSE:
processLinkClose(event.getLink());
break;
case LINK_FLOW:
processLinkFlow(event.getLink());
break;
case DELIVERY:
processDelivery(event.getDelivery());
break;
default:
break;
}
eventCollector.pop();
}
} catch (Throwable e) {
handleException(new AmqpProtocolException("Could not process AMQP commands", true, e));
}
pumpProtonToSocket();
}
protected void processConnectionOpen(Connection connection) throws Exception {
stopConnectionTimeoutChecker();
connectionInfo.setResponseRequired(true);
connectionInfo.setConnectionId(connectionId);
String clientId = protonConnection.getRemoteContainer();
if (clientId != null && !clientId.isEmpty()) {
connectionInfo.setClientId(clientId);
}
connectionInfo.setTransportContext(amqpTransport.getPeerCertificates());
if (connection.getTransport().getRemoteIdleTimeout() > 0 && !amqpTransport.isUseInactivityMonitor()) {
// We cannot meet the requested Idle processing because the inactivity monitor is
// disabled so we won't send idle frames to match the request.
protonConnection.setProperties(getFailedConnetionProperties());
protonConnection.open();
protonConnection.setCondition(new ErrorCondition(AmqpError.PRECONDITION_FAILED, "Cannot send idle frames"));
protonConnection.close();
pumpProtonToSocket();
amqpTransport.onException(new IOException(
"Connection failed, remote requested idle processing but inactivity monitoring is disbaled."));
return;
}
sendToActiveMQ(connectionInfo, new ResponseHandler() {
@Override
public void onResponse(AmqpProtocolConverter converter, Response response) throws IOException {
Throwable exception = null;
try {
if (response.isException()) {
protonConnection.setProperties(getFailedConnetionProperties());
protonConnection.open();
exception = ((ExceptionResponse) response).getException();
if (exception instanceof SecurityException) {
protonConnection.setCondition(new ErrorCondition(AmqpError.UNAUTHORIZED_ACCESS, exception.getMessage()));
} else if (exception instanceof InvalidClientIDException) {
ErrorCondition condition = new ErrorCondition(AmqpError.INVALID_FIELD, exception.getMessage());
Map<Symbol, Object> infoMap = new HashMap<> ();
infoMap.put(INVALID_FIELD, CONTAINER_ID);
condition.setInfo(infoMap);
protonConnection.setCondition(condition);
} else {
protonConnection.setCondition(new ErrorCondition(AmqpError.ILLEGAL_STATE, exception.getMessage()));
}
protonConnection.close();
} else {
if (amqpTransport.isUseInactivityMonitor() && amqpWireFormat.getIdleTimeout() > 0) {
LOG.trace("Connection requesting Idle timeout of: {} mills", amqpWireFormat.getIdleTimeout());
protonTransport.setIdleTimeout(amqpWireFormat.getIdleTimeout());
}
protonConnection.setOfferedCapabilities(getConnectionCapabilitiesOffered());
protonConnection.setProperties(getConnetionProperties());
protonConnection.setContainer(brokerService.getBrokerName());
protonConnection.open();
configureInactivityMonitor();
}
} finally {
pumpProtonToSocket();
if (response.isException()) {
amqpTransport.onException(IOExceptionSupport.create(exception));
}
}
}
});
}
protected void processConnectionClose(Connection connection) throws Exception {
if (!closing) {
closing = true;
sendToActiveMQ(new RemoveInfo(connectionId), new ResponseHandler() {
@Override
public void onResponse(AmqpProtocolConverter converter, Response response) throws IOException {
protonConnection.close();
protonConnection.free();
if (!closedSocket) {
pumpProtonToSocket();
}
}
});
sendToActiveMQ(new ShutdownInfo());
}
}
protected void processSessionOpen(Session protonSession) throws Exception {
new AmqpSession(this, getNextSessionId(), protonSession).open();
}
protected void processSessionClose(Session protonSession) throws Exception {
if (protonSession.getContext() != null) {
((AmqpResource) protonSession.getContext()).close();
} else {
protonSession.close();
protonSession.free();
}
}
protected void processLinkOpen(Link link) throws Exception {
link.setSource(link.getRemoteSource());
link.setTarget(link.getRemoteTarget());
AmqpSession session = (AmqpSession) link.getSession().getContext();
if (link instanceof Receiver) {
if (link.getRemoteTarget() instanceof Coordinator) {
session.createCoordinator((Receiver) link);
} else {
session.createReceiver((Receiver) link);
}
} else {
session.createSender((Sender) link);
}
}
protected void processLinkDetach(Link link) throws Exception {
Object context = link.getContext();
if (context instanceof AmqpLink) {
((AmqpLink) context).detach();
} else {
link.detach();
link.free();
}
}
protected void processLinkClose(Link link) throws Exception {
Object context = link.getContext();
if (context instanceof AmqpLink) {
((AmqpLink) context).close();;
} else {
link.close();
link.free();
}
}
protected void processLinkFlow(Link link) throws Exception {
Object context = link.getContext();
if (context instanceof AmqpLink) {
((AmqpLink) context).flow();
}
}
protected void processDelivery(Delivery delivery) throws Exception {
if (!delivery.isPartial()) {
Object context = delivery.getLink().getContext();
if (context instanceof AmqpLink) {
AmqpLink amqpLink = (AmqpLink) context;
amqpLink.delivery(delivery);
}
}
}
//----- Event entry points for ActiveMQ commands and errors --------------//
@Override
public void onAMQPException(IOException error) {
closedSocket = true;
if (!closing) {
try {
closing = true;
// Attempt to inform the other end that we are going to close
// so that the client doesn't wait around forever.
protonConnection.setCondition(new ErrorCondition(AmqpError.DECODE_ERROR, error.getMessage()));
protonConnection.close();
pumpProtonToSocket();
} catch (Exception ignore) {
}
amqpTransport.sendToActiveMQ(error);
} else {
try {
amqpTransport.stop();
} catch (Exception ignore) {
}
}
}
@Override
public void onActiveMQCommand(Command command) throws Exception {
if (command.isResponse()) {
Response response = (Response) command;
ResponseHandler rh = resposeHandlers.remove(Integer.valueOf(response.getCorrelationId()));
if (rh != null) {
rh.onResponse(this, response);
} else {
// Pass down any unexpected errors. Should this close the connection?
if (response.isException()) {
Throwable exception = ((ExceptionResponse) response).getException();
handleException(exception);
}
}
} else if (command.isMessageDispatch()) {
MessageDispatch dispatch = (MessageDispatch) command;
AmqpSender sender = subscriptionsByConsumerId.get(dispatch.getConsumerId());
if (sender != null) {
// End of Queue Browse will have no Message object.
if (dispatch.getMessage() != null) {
LOG.trace("Dispatching MessageId: {} to consumer", dispatch.getMessage().getMessageId());
} else {
LOG.trace("Dispatching End of Browse Command to consumer {}", dispatch.getConsumerId());
}
sender.onMessageDispatch(dispatch);
if (dispatch.getMessage() != null) {
LOG.trace("Finished Dispatch of MessageId: {} to consumer", dispatch.getMessage().getMessageId());
}
}
} else if (command.getDataStructureType() == ConnectionError.DATA_STRUCTURE_TYPE) {
// Pass down any unexpected async errors. Should this close the connection?
Throwable exception = ((ConnectionError) command).getException();
handleException(exception);
} else if (command.isConsumerControl()) {
ConsumerControl control = (ConsumerControl) command;
AmqpSender sender = subscriptionsByConsumerId.get(control.getConsumerId());
if (sender != null) {
sender.onConsumerControl(control);
}
} else if (command.isBrokerInfo()) {
// ignore
} else {
LOG.debug("Do not know how to process ActiveMQ Command {}", command);
}
}
//----- Utility methods for connection resources to use ------------------//
void registerSender(ConsumerId consumerId, AmqpSender sender) {
subscriptionsByConsumerId.put(consumerId, sender);
}
void unregisterSender(ConsumerId consumerId) {
subscriptionsByConsumerId.remove(consumerId);
}
void registerTransaction(TransactionId txId, AmqpTransactionCoordinator coordinator) {
transactions.put(txId, coordinator);
}
void unregisterTransaction(TransactionId txId) {
transactions.remove(txId);
}
AmqpTransactionCoordinator getTxCoordinator(TransactionId txId) {
return transactions.get(txId);
}
LocalTransactionId getNextTransactionId() {
return new LocalTransactionId(getConnectionId(), ++nextTransactionId);
}
ConsumerInfo lookupSubscription(String subscriptionName) throws AmqpProtocolException {
ConsumerInfo result = null;
RegionBroker regionBroker;
try {
regionBroker = (RegionBroker) brokerService.getBroker().getAdaptor(RegionBroker.class);
} catch (Exception e) {
throw new AmqpProtocolException("Error finding subscription: " + subscriptionName + ": " + e.getMessage(), false, e);
}
final TopicRegion topicRegion = (TopicRegion) regionBroker.getTopicRegion();
DurableTopicSubscription subscription = topicRegion.lookupSubscription(subscriptionName, connectionInfo.getClientId());
if (subscription != null) {
result = subscription.getConsumerInfo();
}
return result;
}
Subscription lookupPrefetchSubscription(ConsumerInfo consumerInfo) {
Subscription subscription = null;
try {
subscription = ((AbstractRegion)((RegionBroker) brokerService.getBroker().getAdaptor(RegionBroker.class)).getRegion(consumerInfo.getDestination())).getSubscriptions().get(consumerInfo.getConsumerId());
} catch (Exception e) {
LOG.warn("Error finding subscription for: " + consumerInfo + ": " + e.getMessage(), false, e);
}
return subscription;
}
ActiveMQDestination createTemporaryDestination(final Link link, Symbol[] capabilities) {
ActiveMQDestination rc = null;
if (contains(capabilities, TEMP_TOPIC_CAPABILITY)) {
rc = new ActiveMQTempTopic(connectionId, nextTempDestinationId++);
} else if (contains(capabilities, TEMP_QUEUE_CAPABILITY)) {
rc = new ActiveMQTempQueue(connectionId, nextTempDestinationId++);
} else {
LOG.debug("Dynamic link request with no type capability, defaults to Temporary Queue");
rc = new ActiveMQTempQueue(connectionId, nextTempDestinationId++);
}
DestinationInfo info = new DestinationInfo();
info.setConnectionId(connectionId);
info.setOperationType(DestinationInfo.ADD_OPERATION_TYPE);
info.setDestination(rc);
sendToActiveMQ(info, new ResponseHandler() {
@Override
public void onResponse(AmqpProtocolConverter converter, Response response) throws IOException {
if (response.isException()) {
link.setSource(null);
Throwable exception = ((ExceptionResponse) response).getException();
if (exception instanceof SecurityException) {
link.setCondition(new ErrorCondition(AmqpError.UNAUTHORIZED_ACCESS, exception.getMessage()));
} else {
link.setCondition(new ErrorCondition(AmqpError.INTERNAL_ERROR, exception.getMessage()));
}
link.close();
link.free();
}
}
});
return rc;
}
void deleteTemporaryDestination(ActiveMQTempDestination destination) {
DestinationInfo info = new DestinationInfo();
info.setConnectionId(connectionId);
info.setOperationType(DestinationInfo.REMOVE_OPERATION_TYPE);
info.setDestination(destination);
sendToActiveMQ(info, new ResponseHandler() {
@Override
public void onResponse(AmqpProtocolConverter converter, Response response) throws IOException {
if (response.isException()) {
Throwable exception = ((ExceptionResponse) response).getException();
LOG.debug("Error during temp destination removeal: {}", exception.getMessage());
}
}
});
}
void sendToActiveMQ(Command command) {
sendToActiveMQ(command, null);
}
void sendToActiveMQ(Command command, ResponseHandler handler) {
command.setCommandId(lastCommandId.incrementAndGet());
if (handler != null) {
command.setResponseRequired(true);
resposeHandlers.put(Integer.valueOf(command.getCommandId()), handler);
}
amqpTransport.sendToActiveMQ(command);
}
void handleException(Throwable exception) {
LOG.debug("Exception detail", exception);
if (exception instanceof AmqpProtocolException) {
onAMQPException((IOException) exception);
} else {
try {
// Must ensure that the broker removes Connection resources.
sendToActiveMQ(new ShutdownInfo());
amqpTransport.stop();
} catch (Throwable e) {
LOG.error("Failed to stop AMQP Transport ", e);
}
}
}
//----- Internal implementation ------------------------------------------//
private SessionId getNextSessionId() {
return new SessionId(connectionId, nextSessionId++);
}
private void stopConnectionTimeoutChecker() {
AmqpInactivityMonitor monitor = amqpTransport.getInactivityMonitor();
if (monitor != null) {
monitor.stopConnectionTimeoutChecker();
}
}
private void configureInactivityMonitor() {
AmqpInactivityMonitor monitor = amqpTransport.getInactivityMonitor();
if (monitor == null) {
return;
}
// If either end has idle timeout requirements then the tick method
// will give us a deadline on the next time we need to tick() in order
// to meet those obligations.
// Using nano time since it is not related to the wall clock, which may change
long now = TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
long nextIdleCheck = protonTransport.tick(now);
if (nextIdleCheck > 0) {
long delay = nextIdleCheck - now;
LOG.trace("Connection keep-alive processing starts in: {}", delay);
monitor.startKeepAliveTask(delay);
} else {
LOG.trace("Connection does not require keep-alive processing");
}
}
}