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.toLong; 020 021import java.io.IOException; 022 023import javax.jms.Destination; 024import javax.jms.ResourceAllocationException; 025 026import org.apache.activemq.command.ActiveMQDestination; 027import org.apache.activemq.command.ActiveMQMessage; 028import org.apache.activemq.command.ExceptionResponse; 029import org.apache.activemq.command.LocalTransactionId; 030import org.apache.activemq.command.MessageId; 031import org.apache.activemq.command.ProducerId; 032import org.apache.activemq.command.ProducerInfo; 033import org.apache.activemq.command.RemoveInfo; 034import org.apache.activemq.command.Response; 035import org.apache.activemq.command.TransactionId; 036import org.apache.activemq.transport.amqp.AmqpProtocolConverter; 037import org.apache.activemq.transport.amqp.ResponseHandler; 038import org.apache.activemq.transport.amqp.message.AMQPNativeInboundTransformer; 039import org.apache.activemq.transport.amqp.message.AMQPRawInboundTransformer; 040import org.apache.activemq.transport.amqp.message.EncodedMessage; 041import org.apache.activemq.transport.amqp.message.InboundTransformer; 042import org.apache.activemq.transport.amqp.message.JMSMappingInboundTransformer; 043import org.apache.activemq.util.LongSequenceGenerator; 044import org.apache.qpid.proton.amqp.Symbol; 045import org.apache.qpid.proton.amqp.messaging.Accepted; 046import org.apache.qpid.proton.amqp.messaging.Rejected; 047import org.apache.qpid.proton.amqp.transaction.TransactionalState; 048import org.apache.qpid.proton.amqp.transport.AmqpError; 049import org.apache.qpid.proton.amqp.transport.DeliveryState; 050import org.apache.qpid.proton.amqp.transport.ErrorCondition; 051import org.apache.qpid.proton.engine.Delivery; 052import org.apache.qpid.proton.engine.Receiver; 053import org.fusesource.hawtbuf.Buffer; 054import org.slf4j.Logger; 055import org.slf4j.LoggerFactory; 056 057/** 058 * An AmqpReceiver wraps the AMQP Receiver end of a link from the remote peer 059 * which holds the corresponding Sender which transfers message accross the 060 * link. The AmqpReceiver handles all incoming deliveries by converting them 061 * or wrapping them into an ActiveMQ message object and forwarding that message 062 * on to the appropriate ActiveMQ Destination. 063 */ 064public class AmqpReceiver extends AmqpAbstractReceiver { 065 066 private static final Logger LOG = LoggerFactory.getLogger(AmqpReceiver.class); 067 068 private final ProducerInfo producerInfo; 069 private final LongSequenceGenerator messageIdGenerator = new LongSequenceGenerator(); 070 071 private InboundTransformer inboundTransformer; 072 073 private int sendsInFlight; 074 075 /** 076 * Create a new instance of an AmqpReceiver 077 * 078 * @param session 079 * the Session that is the parent of this AmqpReceiver instance. 080 * @param endpoint 081 * the AMQP receiver endpoint that the class manages. 082 * @param producerInfo 083 * the ProducerInfo instance that contains this sender's configuration. 084 */ 085 public AmqpReceiver(AmqpSession session, Receiver endpoint, ProducerInfo producerInfo) { 086 super(session, endpoint); 087 088 this.producerInfo = producerInfo; 089 } 090 091 @Override 092 public void close() { 093 if (!isClosed() && isOpened()) { 094 sendToActiveMQ(new RemoveInfo(getProducerId()), new ResponseHandler() { 095 096 @Override 097 public void onResponse(AmqpProtocolConverter converter, Response response) throws IOException { 098 AmqpReceiver.super.close(); 099 } 100 }); 101 } else { 102 super.close(); 103 } 104 } 105 106 //----- Configuration accessors ------------------------------------------// 107 108 /** 109 * @return the ActiveMQ ProducerId used to register this Receiver on the Broker. 110 */ 111 public ProducerId getProducerId() { 112 return producerInfo.getProducerId(); 113 } 114 115 @Override 116 public ActiveMQDestination getDestination() { 117 return producerInfo.getDestination(); 118 } 119 120 @Override 121 public void setDestination(ActiveMQDestination destination) { 122 producerInfo.setDestination(destination); 123 } 124 125 /** 126 * If the Sender that initiated this Receiver endpoint did not define an address 127 * then it is using anonymous mode and message are to be routed to the address 128 * that is defined in the AMQP message 'To' field. 129 * 130 * @return true if this Receiver should operate in anonymous mode. 131 */ 132 public boolean isAnonymous() { 133 return producerInfo.getDestination() == null; 134 } 135 136 //----- Internal Implementation ------------------------------------------// 137 138 protected InboundTransformer getTransformer() { 139 if (inboundTransformer == null) { 140 String transformer = session.getConnection().getConfiguredTransformer(); 141 if (transformer.equalsIgnoreCase(InboundTransformer.TRANSFORMER_JMS)) { 142 inboundTransformer = new JMSMappingInboundTransformer(); 143 } else if (transformer.equalsIgnoreCase(InboundTransformer.TRANSFORMER_NATIVE)) { 144 inboundTransformer = new AMQPNativeInboundTransformer(); 145 } else if (transformer.equalsIgnoreCase(InboundTransformer.TRANSFORMER_RAW)) { 146 inboundTransformer = new AMQPRawInboundTransformer(); 147 } else { 148 LOG.warn("Unknown transformer type {} using native one instead", transformer); 149 inboundTransformer = new AMQPNativeInboundTransformer(); 150 } 151 } 152 return inboundTransformer; 153 } 154 155 @Override 156 protected void processDelivery(final Delivery delivery, Buffer deliveryBytes) throws Exception { 157 if (!isClosed()) { 158 EncodedMessage em = new EncodedMessage(delivery.getMessageFormat(), deliveryBytes.data, deliveryBytes.offset, deliveryBytes.length); 159 160 InboundTransformer transformer = getTransformer(); 161 ActiveMQMessage message = transformer.transform(em); 162 163 current = null; 164 165 if (isAnonymous()) { 166 Destination toDestination = message.getJMSDestination(); 167 if (toDestination == null || !(toDestination instanceof ActiveMQDestination)) { 168 Rejected rejected = new Rejected(); 169 ErrorCondition condition = new ErrorCondition(); 170 condition.setCondition(Symbol.valueOf("failed")); 171 condition.setDescription("Missing to field for message sent to an anonymous producer"); 172 rejected.setError(condition); 173 delivery.disposition(rejected); 174 return; 175 } 176 } else { 177 message.setJMSDestination(getDestination()); 178 } 179 180 message.setProducerId(getProducerId()); 181 182 // Always override the AMQP client's MessageId with our own. Preserve 183 // the original in the TextView property for later Ack. 184 MessageId messageId = new MessageId(getProducerId(), messageIdGenerator.getNextSequenceId()); 185 186 MessageId amqpMessageId = message.getMessageId(); 187 if (amqpMessageId != null) { 188 if (amqpMessageId.getTextView() != null) { 189 messageId.setTextView(amqpMessageId.getTextView()); 190 } else { 191 messageId.setTextView(amqpMessageId.toString()); 192 } 193 } 194 195 message.setMessageId(messageId); 196 197 LOG.trace("Inbound Message:{} from Producer:{}", 198 message.getMessageId(), getProducerId() + ":" + messageId.getProducerSequenceId()); 199 200 final DeliveryState remoteState = delivery.getRemoteState(); 201 if (remoteState != null && remoteState instanceof TransactionalState) { 202 TransactionalState txState = (TransactionalState) remoteState; 203 TransactionId txId = new LocalTransactionId(session.getConnection().getConnectionId(), toLong(txState.getTxnId())); 204 session.enlist(txId); 205 message.setTransactionId(txId); 206 } 207 208 message.onSend(); 209 210 sendsInFlight++; 211 212 sendToActiveMQ(message, createResponseHandler(delivery)); 213 } 214 } 215 216 private ResponseHandler createResponseHandler(final Delivery delivery) { 217 return new ResponseHandler() { 218 219 @Override 220 public void onResponse(AmqpProtocolConverter converter, Response response) throws IOException { 221 if (!delivery.remotelySettled()) { 222 if (response.isException()) { 223 ExceptionResponse error = (ExceptionResponse) response; 224 Rejected rejected = new Rejected(); 225 ErrorCondition condition = new ErrorCondition(); 226 227 if (error.getException() instanceof SecurityException) { 228 condition.setCondition(AmqpError.UNAUTHORIZED_ACCESS); 229 } else if (error.getException() instanceof ResourceAllocationException) { 230 condition.setCondition(AmqpError.RESOURCE_LIMIT_EXCEEDED); 231 } else { 232 condition.setCondition(Symbol.valueOf("failed")); 233 } 234 235 condition.setDescription(error.getException().getMessage()); 236 rejected.setError(condition); 237 delivery.disposition(rejected); 238 } else { 239 final DeliveryState remoteState = delivery.getRemoteState(); 240 if (remoteState != null && remoteState instanceof TransactionalState) { 241 TransactionalState txAccepted = new TransactionalState(); 242 txAccepted.setOutcome(Accepted.getInstance()); 243 txAccepted.setTxnId(((TransactionalState) remoteState).getTxnId()); 244 245 delivery.disposition(txAccepted); 246 } else { 247 delivery.disposition(Accepted.getInstance()); 248 } 249 } 250 } 251 252 if (getEndpoint().getCredit() + --sendsInFlight <= (getConfiguredReceiverCredit() * .3)) { 253 LOG.trace("Sending more credit ({}) to producer: {}", getConfiguredReceiverCredit() * .7, getProducerId()); 254 getEndpoint().flow((int) (getConfiguredReceiverCredit() * .7)); 255 } 256 257 delivery.settle(); 258 session.pumpProtonToSocket(); 259 } 260 }; 261 } 262}