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}