/*
* 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.toLong;
import java.io.IOException;
import javax.jms.Destination;
import javax.jms.ResourceAllocationException;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.ActiveMQMessage;
import org.apache.activemq.command.ExceptionResponse;
import org.apache.activemq.command.LocalTransactionId;
import org.apache.activemq.command.MessageId;
import org.apache.activemq.command.ProducerId;
import org.apache.activemq.command.ProducerInfo;
import org.apache.activemq.command.RemoveInfo;
import org.apache.activemq.command.Response;
import org.apache.activemq.command.TransactionId;
import org.apache.activemq.transport.amqp.AmqpProtocolConverter;
import org.apache.activemq.transport.amqp.ResponseHandler;
import org.apache.activemq.transport.amqp.message.AMQPNativeInboundTransformer;
import org.apache.activemq.transport.amqp.message.AMQPRawInboundTransformer;
import org.apache.activemq.transport.amqp.message.EncodedMessage;
import org.apache.activemq.transport.amqp.message.InboundTransformer;
import org.apache.activemq.transport.amqp.message.JMSMappingInboundTransformer;
import org.apache.activemq.util.LongSequenceGenerator;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.Accepted;
import org.apache.qpid.proton.amqp.messaging.Rejected;
import org.apache.qpid.proton.amqp.transaction.TransactionalState;
import org.apache.qpid.proton.amqp.transport.AmqpError;
import org.apache.qpid.proton.amqp.transport.DeliveryState;
import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.apache.qpid.proton.engine.Delivery;
import org.apache.qpid.proton.engine.Receiver;
import org.fusesource.hawtbuf.Buffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An AmqpReceiver wraps the AMQP Receiver end of a link from the remote peer
* which holds the corresponding Sender which transfers message accross the
* link. The AmqpReceiver handles all incoming deliveries by converting them
* or wrapping them into an ActiveMQ message object and forwarding that message
* on to the appropriate ActiveMQ Destination.
*/
public class AmqpReceiver extends AmqpAbstractReceiver {
private static final Logger LOG = LoggerFactory.getLogger(AmqpReceiver.class);
private final ProducerInfo producerInfo;
private final LongSequenceGenerator messageIdGenerator = new LongSequenceGenerator();
private InboundTransformer inboundTransformer;
private int sendsInFlight;
/**
* Create a new instance of an AmqpReceiver
*
* @param session
* the Session that is the parent of this AmqpReceiver instance.
* @param endpoint
* the AMQP receiver endpoint that the class manages.
* @param producerInfo
* the ProducerInfo instance that contains this sender's configuration.
*/
public AmqpReceiver(AmqpSession session, Receiver endpoint, ProducerInfo producerInfo) {
super(session, endpoint);
this.producerInfo = producerInfo;
}
@Override
public void close() {
if (!isClosed() && isOpened()) {
sendToActiveMQ(new RemoveInfo(getProducerId()), new ResponseHandler() {
@Override
public void onResponse(AmqpProtocolConverter converter, Response response) throws IOException {
AmqpReceiver.super.close();
}
});
} else {
super.close();
}
}
//----- Configuration accessors ------------------------------------------//
/**
* @return the ActiveMQ ProducerId used to register this Receiver on the Broker.
*/
public ProducerId getProducerId() {
return producerInfo.getProducerId();
}
@Override
public ActiveMQDestination getDestination() {
return producerInfo.getDestination();
}
@Override
public void setDestination(ActiveMQDestination destination) {
producerInfo.setDestination(destination);
}
/**
* If the Sender that initiated this Receiver endpoint did not define an address
* then it is using anonymous mode and message are to be routed to the address
* that is defined in the AMQP message 'To' field.
*
* @return true if this Receiver should operate in anonymous mode.
*/
public boolean isAnonymous() {
return producerInfo.getDestination() == null;
}
//----- Internal Implementation ------------------------------------------//
protected InboundTransformer getTransformer() {
if (inboundTransformer == null) {
String transformer = session.getConnection().getConfiguredTransformer();
if (transformer.equalsIgnoreCase(InboundTransformer.TRANSFORMER_JMS)) {
inboundTransformer = new JMSMappingInboundTransformer();
} else if (transformer.equalsIgnoreCase(InboundTransformer.TRANSFORMER_NATIVE)) {
inboundTransformer = new AMQPNativeInboundTransformer();
} else if (transformer.equalsIgnoreCase(InboundTransformer.TRANSFORMER_RAW)) {
inboundTransformer = new AMQPRawInboundTransformer();
} else {
LOG.warn("Unknown transformer type {} using native one instead", transformer);
inboundTransformer = new AMQPNativeInboundTransformer();
}
}
return inboundTransformer;
}
@Override
protected void processDelivery(final Delivery delivery, Buffer deliveryBytes) throws Exception {
if (!isClosed()) {
EncodedMessage em = new EncodedMessage(delivery.getMessageFormat(), deliveryBytes.data, deliveryBytes.offset, deliveryBytes.length);
InboundTransformer transformer = getTransformer();
ActiveMQMessage message = transformer.transform(em);
current = null;
if (isAnonymous()) {
Destination toDestination = message.getJMSDestination();
if (toDestination == null || !(toDestination instanceof ActiveMQDestination)) {
Rejected rejected = new Rejected();
ErrorCondition condition = new ErrorCondition();
condition.setCondition(Symbol.valueOf("failed"));
condition.setDescription("Missing to field for message sent to an anonymous producer");
rejected.setError(condition);
delivery.disposition(rejected);
return;
}
} else {
message.setJMSDestination(getDestination());
}
message.setProducerId(getProducerId());
// Always override the AMQP client's MessageId with our own. Preserve
// the original in the TextView property for later Ack.
MessageId messageId = new MessageId(getProducerId(), messageIdGenerator.getNextSequenceId());
MessageId amqpMessageId = message.getMessageId();
if (amqpMessageId != null) {
if (amqpMessageId.getTextView() != null) {
messageId.setTextView(amqpMessageId.getTextView());
} else {
messageId.setTextView(amqpMessageId.toString());
}
}
message.setMessageId(messageId);
LOG.trace("Inbound Message:{} from Producer:{}",
message.getMessageId(), getProducerId() + ":" + messageId.getProducerSequenceId());
final DeliveryState remoteState = delivery.getRemoteState();
if (remoteState != null && remoteState instanceof TransactionalState) {
TransactionalState txState = (TransactionalState) remoteState;
TransactionId txId = new LocalTransactionId(session.getConnection().getConnectionId(), toLong(txState.getTxnId()));
session.enlist(txId);
message.setTransactionId(txId);
}
message.onSend();
sendsInFlight++;
sendToActiveMQ(message, createResponseHandler(delivery));
}
}
private ResponseHandler createResponseHandler(final Delivery delivery) {
return new ResponseHandler() {
@Override
public void onResponse(AmqpProtocolConverter converter, Response response) throws IOException {
if (!delivery.remotelySettled()) {
if (response.isException()) {
ExceptionResponse error = (ExceptionResponse) response;
Rejected rejected = new Rejected();
ErrorCondition condition = new ErrorCondition();
if (error.getException() instanceof SecurityException) {
condition.setCondition(AmqpError.UNAUTHORIZED_ACCESS);
} else if (error.getException() instanceof ResourceAllocationException) {
condition.setCondition(AmqpError.RESOURCE_LIMIT_EXCEEDED);
} else {
condition.setCondition(Symbol.valueOf("failed"));
}
condition.setDescription(error.getException().getMessage());
rejected.setError(condition);
delivery.disposition(rejected);
} else {
final DeliveryState remoteState = delivery.getRemoteState();
if (remoteState != null && remoteState instanceof TransactionalState) {
TransactionalState txAccepted = new TransactionalState();
txAccepted.setOutcome(Accepted.getInstance());
txAccepted.setTxnId(((TransactionalState) remoteState).getTxnId());
delivery.disposition(txAccepted);
} else {
delivery.disposition(Accepted.getInstance());
}
}
}
if (getEndpoint().getCredit() + --sendsInFlight <= (getConfiguredReceiverCredit() * .3)) {
LOG.trace("Sending more credit ({}) to producer: {}", getConfiguredReceiverCredit() * .7, getProducerId());
getEndpoint().flow((int) (getConfiguredReceiverCredit() * .7));
}
delivery.settle();
session.pumpProtonToSocket();
}
};
}
}