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.mqtt;
018
019import java.io.IOException;
020import java.util.Map;
021import java.util.concurrent.ConcurrentHashMap;
022import java.util.concurrent.ConcurrentMap;
023import java.util.concurrent.atomic.AtomicBoolean;
024import java.util.zip.DataFormatException;
025import java.util.zip.Inflater;
026
027import javax.jms.Destination;
028import javax.jms.InvalidClientIDException;
029import javax.jms.JMSException;
030import javax.jms.Message;
031import javax.security.auth.login.CredentialException;
032
033import org.apache.activemq.broker.BrokerService;
034import org.apache.activemq.broker.BrokerServiceAware;
035import org.apache.activemq.broker.region.policy.RetainedMessageSubscriptionRecoveryPolicy;
036import org.apache.activemq.command.ActiveMQBytesMessage;
037import org.apache.activemq.command.ActiveMQDestination;
038import org.apache.activemq.command.ActiveMQMapMessage;
039import org.apache.activemq.command.ActiveMQMessage;
040import org.apache.activemq.command.ActiveMQTextMessage;
041import org.apache.activemq.command.Command;
042import org.apache.activemq.command.ConnectionError;
043import org.apache.activemq.command.ConnectionId;
044import org.apache.activemq.command.ConnectionInfo;
045import org.apache.activemq.command.ExceptionResponse;
046import org.apache.activemq.command.MessageAck;
047import org.apache.activemq.command.MessageDispatch;
048import org.apache.activemq.command.MessageId;
049import org.apache.activemq.command.ProducerId;
050import org.apache.activemq.command.ProducerInfo;
051import org.apache.activemq.command.Response;
052import org.apache.activemq.command.SessionId;
053import org.apache.activemq.command.SessionInfo;
054import org.apache.activemq.command.ShutdownInfo;
055import org.apache.activemq.transport.mqtt.strategy.MQTTSubscriptionStrategy;
056import org.apache.activemq.util.ByteArrayOutputStream;
057import org.apache.activemq.util.ByteSequence;
058import org.apache.activemq.util.FactoryFinder;
059import org.apache.activemq.util.IOExceptionSupport;
060import org.apache.activemq.util.IdGenerator;
061import org.apache.activemq.util.JMSExceptionSupport;
062import org.apache.activemq.util.LRUCache;
063import org.apache.activemq.util.LongSequenceGenerator;
064import org.fusesource.hawtbuf.Buffer;
065import org.fusesource.hawtbuf.UTF8Buffer;
066import org.fusesource.mqtt.client.QoS;
067import org.fusesource.mqtt.client.Topic;
068import org.fusesource.mqtt.codec.CONNACK;
069import org.fusesource.mqtt.codec.CONNECT;
070import org.fusesource.mqtt.codec.DISCONNECT;
071import org.fusesource.mqtt.codec.MQTTFrame;
072import org.fusesource.mqtt.codec.PINGREQ;
073import org.fusesource.mqtt.codec.PINGRESP;
074import org.fusesource.mqtt.codec.PUBACK;
075import org.fusesource.mqtt.codec.PUBCOMP;
076import org.fusesource.mqtt.codec.PUBLISH;
077import org.fusesource.mqtt.codec.PUBREC;
078import org.fusesource.mqtt.codec.PUBREL;
079import org.fusesource.mqtt.codec.SUBACK;
080import org.fusesource.mqtt.codec.SUBSCRIBE;
081import org.fusesource.mqtt.codec.UNSUBACK;
082import org.fusesource.mqtt.codec.UNSUBSCRIBE;
083import org.slf4j.Logger;
084import org.slf4j.LoggerFactory;
085
086public class MQTTProtocolConverter {
087
088    private static final Logger LOG = LoggerFactory.getLogger(MQTTProtocolConverter.class);
089
090    public static final String QOS_PROPERTY_NAME = "ActiveMQ.MQTT.QoS";
091    public static final int V3_1 = 3;
092    public static final int V3_1_1 = 4;
093
094    private static final IdGenerator CONNECTION_ID_GENERATOR = new IdGenerator();
095    private static final MQTTFrame PING_RESP_FRAME = new PINGRESP().encode();
096    private static final double MQTT_KEEP_ALIVE_GRACE_PERIOD = 0.5;
097    static final int DEFAULT_CACHE_SIZE = 5000;
098
099    private final ConnectionId connectionId = new ConnectionId(CONNECTION_ID_GENERATOR.generateId());
100    private final SessionId sessionId = new SessionId(connectionId, -1);
101    private final ProducerId producerId = new ProducerId(sessionId, 1);
102    private final LongSequenceGenerator publisherIdGenerator = new LongSequenceGenerator();
103
104    private final ConcurrentMap<Integer, ResponseHandler> resposeHandlers = new ConcurrentHashMap<Integer, ResponseHandler>();
105    private final Map<String, ActiveMQDestination> activeMQDestinationMap = new LRUCache<String, ActiveMQDestination>(DEFAULT_CACHE_SIZE);
106    private final Map<Destination, String> mqttTopicMap = new LRUCache<Destination, String>(DEFAULT_CACHE_SIZE);
107
108    private final Map<Short, MessageAck> consumerAcks = new LRUCache<Short, MessageAck>(DEFAULT_CACHE_SIZE);
109    private final Map<Short, PUBREC> publisherRecs = new LRUCache<Short, PUBREC>(DEFAULT_CACHE_SIZE);
110
111    private final MQTTTransport mqttTransport;
112    private final BrokerService brokerService;
113
114    private final Object commnadIdMutex = new Object();
115    private int lastCommandId;
116    private final AtomicBoolean connected = new AtomicBoolean(false);
117    private final ConnectionInfo connectionInfo = new ConnectionInfo();
118    private CONNECT connect;
119    private String clientId;
120    private long defaultKeepAlive;
121    private int activeMQSubscriptionPrefetch = -1;
122    private final MQTTPacketIdGenerator packetIdGenerator;
123    private boolean publishDollarTopics;
124
125    public int version;
126
127    private final FactoryFinder STRATAGY_FINDER = new FactoryFinder("META-INF/services/org/apache/activemq/transport/strategies/");
128
129    /*
130     * Subscription strategy configuration element.
131     *   > mqtt-default-subscriptions
132     *   > mqtt-virtual-topic-subscriptions
133     */
134    private String subscriptionStrategyName = "mqtt-default-subscriptions";
135    private MQTTSubscriptionStrategy subsciptionStrategy;
136
137    public MQTTProtocolConverter(MQTTTransport mqttTransport, BrokerService brokerService) {
138        this.mqttTransport = mqttTransport;
139        this.brokerService = brokerService;
140        this.packetIdGenerator = MQTTPacketIdGenerator.getMQTTPacketIdGenerator(brokerService);
141        this.defaultKeepAlive = 0;
142    }
143
144    int generateCommandId() {
145        synchronized (commnadIdMutex) {
146            return lastCommandId++;
147        }
148    }
149
150    public void sendToActiveMQ(Command command, ResponseHandler handler) {
151
152        // Lets intercept message send requests..
153        if (command instanceof ActiveMQMessage) {
154            ActiveMQMessage msg = (ActiveMQMessage) command;
155            try {
156                if (!getPublishDollarTopics() && findSubscriptionStrategy().isControlTopic(msg.getDestination())) {
157                    // We don't allow users to send to $ prefixed topics to avoid failing MQTT 3.1.1
158                    // specification requirements for system assigned destinations.
159                    if (handler != null) {
160                        try {
161                            handler.onResponse(this, new Response());
162                        } catch (IOException e) {
163                            LOG.warn("Failed to send command " + command, e);
164                        }
165                    }
166                    return;
167                }
168            } catch (IOException e) {
169                LOG.warn("Failed to send command " + command, e);
170            }
171        }
172
173        command.setCommandId(generateCommandId());
174        if (handler != null) {
175            command.setResponseRequired(true);
176            resposeHandlers.put(command.getCommandId(), handler);
177        }
178        getMQTTTransport().sendToActiveMQ(command);
179    }
180
181    void sendToMQTT(MQTTFrame frame) {
182        try {
183            mqttTransport.sendToMQTT(frame);
184        } catch (IOException e) {
185            LOG.warn("Failed to send frame " + frame, e);
186        }
187    }
188
189    /**
190     * Convert a MQTT command
191     */
192    public void onMQTTCommand(MQTTFrame frame) throws IOException, JMSException {
193        switch (frame.messageType()) {
194            case PINGREQ.TYPE:
195                LOG.debug("Received a ping from client: " + getClientId());
196                checkConnected();
197                sendToMQTT(PING_RESP_FRAME);
198                LOG.debug("Sent Ping Response to " + getClientId());
199                break;
200            case CONNECT.TYPE:
201                CONNECT connect = new CONNECT().decode(frame);
202                onMQTTConnect(connect);
203                LOG.debug("MQTT Client {} connected. (version: {})", getClientId(), connect.version());
204                break;
205            case DISCONNECT.TYPE:
206                LOG.debug("MQTT Client {} disconnecting", getClientId());
207                onMQTTDisconnect();
208                break;
209            case SUBSCRIBE.TYPE:
210                onSubscribe(new SUBSCRIBE().decode(frame));
211                break;
212            case UNSUBSCRIBE.TYPE:
213                onUnSubscribe(new UNSUBSCRIBE().decode(frame));
214                break;
215            case PUBLISH.TYPE:
216                onMQTTPublish(new PUBLISH().decode(frame));
217                break;
218            case PUBACK.TYPE:
219                onMQTTPubAck(new PUBACK().decode(frame));
220                break;
221            case PUBREC.TYPE:
222                onMQTTPubRec(new PUBREC().decode(frame));
223                break;
224            case PUBREL.TYPE:
225                onMQTTPubRel(new PUBREL().decode(frame));
226                break;
227            case PUBCOMP.TYPE:
228                onMQTTPubComp(new PUBCOMP().decode(frame));
229                break;
230            default:
231                handleException(new MQTTProtocolException("Unknown MQTTFrame type: " + frame.messageType(), true), frame);
232        }
233    }
234
235    void onMQTTConnect(final CONNECT connect) throws MQTTProtocolException {
236        if (connected.get()) {
237            throw new MQTTProtocolException("Already connected.");
238        }
239        this.connect = connect;
240
241        // The Server MUST respond to the CONNECT Packet with a CONNACK return code 0x01
242        // (unacceptable protocol level) and then disconnect the Client if the Protocol Level
243        // is not supported by the Server [MQTT-3.1.2-2].
244        if (connect.version() < 3 || connect.version() > 4) {
245            CONNACK ack = new CONNACK();
246            ack.code(CONNACK.Code.CONNECTION_REFUSED_UNACCEPTED_PROTOCOL_VERSION);
247            try {
248                getMQTTTransport().sendToMQTT(ack.encode());
249                getMQTTTransport().onException(IOExceptionSupport.create("Unsupported or invalid protocol version", null));
250            } catch (IOException e) {
251                getMQTTTransport().onException(IOExceptionSupport.create(e));
252            }
253            return;
254        }
255
256        String clientId = "";
257        if (connect.clientId() != null) {
258            clientId = connect.clientId().toString();
259        }
260
261        String userName = null;
262        if (connect.userName() != null) {
263            userName = connect.userName().toString();
264        }
265        String passswd = null;
266        if (connect.password() != null) {
267            passswd = connect.password().toString();
268        }
269
270        version = connect.version();
271
272        configureInactivityMonitor(connect.keepAlive());
273
274        connectionInfo.setConnectionId(connectionId);
275        if (clientId != null && !clientId.isEmpty()) {
276            connectionInfo.setClientId(clientId);
277        } else {
278            // Clean Session MUST be set for 0 length Client Id
279            if (!connect.cleanSession()) {
280                CONNACK ack = new CONNACK();
281                ack.code(CONNACK.Code.CONNECTION_REFUSED_IDENTIFIER_REJECTED);
282                try {
283                    getMQTTTransport().sendToMQTT(ack.encode());
284                    getMQTTTransport().onException(IOExceptionSupport.create("Invalid Client ID", null));
285                } catch (IOException e) {
286                    getMQTTTransport().onException(IOExceptionSupport.create(e));
287                }
288                return;
289            }
290            connectionInfo.setClientId("" + connectionInfo.getConnectionId().toString());
291        }
292
293        connectionInfo.setResponseRequired(true);
294        connectionInfo.setUserName(userName);
295        connectionInfo.setPassword(passswd);
296        connectionInfo.setTransportContext(mqttTransport.getPeerCertificates());
297
298        sendToActiveMQ(connectionInfo, new ResponseHandler() {
299            @Override
300            public void onResponse(MQTTProtocolConverter converter, Response response) throws IOException {
301
302                if (response.isException()) {
303                    // If the connection attempt fails we close the socket.
304                    Throwable exception = ((ExceptionResponse) response).getException();
305                    //let the client know
306                    CONNACK ack = new CONNACK();
307                    if (exception instanceof InvalidClientIDException) {
308                        ack.code(CONNACK.Code.CONNECTION_REFUSED_IDENTIFIER_REJECTED);
309                    } else if (exception instanceof SecurityException) {
310                        ack.code(CONNACK.Code.CONNECTION_REFUSED_NOT_AUTHORIZED);
311                    } else if (exception instanceof CredentialException) {
312                        ack.code(CONNACK.Code.CONNECTION_REFUSED_BAD_USERNAME_OR_PASSWORD);
313                    } else {
314                        ack.code(CONNACK.Code.CONNECTION_REFUSED_SERVER_UNAVAILABLE);
315                    }
316                    getMQTTTransport().sendToMQTT(ack.encode());
317                    getMQTTTransport().onException(IOExceptionSupport.create(exception));
318                    return;
319                }
320
321                final SessionInfo sessionInfo = new SessionInfo(sessionId);
322                sendToActiveMQ(sessionInfo, null);
323
324                final ProducerInfo producerInfo = new ProducerInfo(producerId);
325                sendToActiveMQ(producerInfo, new ResponseHandler() {
326                    @Override
327                    public void onResponse(MQTTProtocolConverter converter, Response response) throws IOException {
328
329                        if (response.isException()) {
330                            // If the connection attempt fails we close the socket.
331                            Throwable exception = ((ExceptionResponse) response).getException();
332                            CONNACK ack = new CONNACK();
333                            ack.code(CONNACK.Code.CONNECTION_REFUSED_BAD_USERNAME_OR_PASSWORD);
334                            getMQTTTransport().sendToMQTT(ack.encode());
335                            getMQTTTransport().onException(IOExceptionSupport.create(exception));
336                            return;
337                        }
338
339                        CONNACK ack = new CONNACK();
340                        ack.code(CONNACK.Code.CONNECTION_ACCEPTED);
341                        connected.set(true);
342                        getMQTTTransport().sendToMQTT(ack.encode());
343
344                        if (connect.cleanSession()) {
345                            packetIdGenerator.stopClientSession(getClientId());
346                        } else {
347                            packetIdGenerator.startClientSession(getClientId());
348                        }
349
350                        findSubscriptionStrategy().onConnect(connect);
351                    }
352                });
353            }
354        });
355    }
356
357    void onMQTTDisconnect() throws MQTTProtocolException {
358        if (connected.compareAndSet(true, false)) {
359            sendToActiveMQ(connectionInfo.createRemoveCommand(), null);
360            sendToActiveMQ(new ShutdownInfo(), null);
361        }
362        stopTransport();
363    }
364
365    void onSubscribe(SUBSCRIBE command) throws MQTTProtocolException {
366        checkConnected();
367        LOG.trace("MQTT SUBSCRIBE message:{} client:{} connection:{}",
368                  command.messageId(), clientId, connectionInfo.getConnectionId());
369        Topic[] topics = command.topics();
370        if (topics != null) {
371            byte[] qos = new byte[topics.length];
372            for (int i = 0; i < topics.length; i++) {
373                try {
374                    qos[i] = findSubscriptionStrategy().onSubscribe(topics[i]);
375                } catch (IOException e) {
376                    throw new MQTTProtocolException("Failed to process subscription request", true, e);
377                }
378            }
379            SUBACK ack = new SUBACK();
380            ack.messageId(command.messageId());
381            ack.grantedQos(qos);
382            try {
383                getMQTTTransport().sendToMQTT(ack.encode());
384            } catch (IOException e) {
385                LOG.warn("Couldn't send SUBACK for " + command, e);
386            }
387        } else {
388            LOG.warn("No topics defined for Subscription " + command);
389        }
390    }
391
392    public void onUnSubscribe(UNSUBSCRIBE command) throws MQTTProtocolException {
393        checkConnected();
394        if (command.qos() != QoS.AT_LEAST_ONCE && (version != V3_1 || publishDollarTopics != true)) {
395            throw new MQTTProtocolException("Failed to process unsubscribe request", true, new Exception("UNSUBSCRIBE frame not properly formatted, QoS"));
396        }
397        UTF8Buffer[] topics = command.topics();
398        if (topics != null) {
399            for (UTF8Buffer topic : topics) {
400                try {
401                    findSubscriptionStrategy().onUnSubscribe(topic.toString());
402                } catch (IOException e) {
403                    throw new MQTTProtocolException("Failed to process unsubscribe request", true, e);
404                }
405            }
406        }
407        UNSUBACK ack = new UNSUBACK();
408        ack.messageId(command.messageId());
409        sendToMQTT(ack.encode());
410    }
411
412    /**
413     * Dispatch an ActiveMQ command
414     */
415    public void onActiveMQCommand(Command command) throws Exception {
416        if (command.isResponse()) {
417            Response response = (Response) command;
418            ResponseHandler rh = resposeHandlers.remove(Integer.valueOf(response.getCorrelationId()));
419            if (rh != null) {
420                rh.onResponse(this, response);
421            } else {
422                // Pass down any unexpected errors. Should this close the connection?
423                if (response.isException()) {
424                    Throwable exception = ((ExceptionResponse) response).getException();
425                    handleException(exception, null);
426                }
427            }
428        } else if (command.isMessageDispatch()) {
429            MessageDispatch md = (MessageDispatch) command;
430            MQTTSubscription sub = findSubscriptionStrategy().getSubscription(md.getConsumerId());
431            if (sub != null) {
432                MessageAck ack = sub.createMessageAck(md);
433                PUBLISH publish = sub.createPublish((ActiveMQMessage) md.getMessage());
434                switch (publish.qos()) {
435                    case AT_LEAST_ONCE:
436                    case EXACTLY_ONCE:
437                        publish.dup(publish.dup() ? true : md.getMessage().isRedelivered());
438                    case AT_MOST_ONCE:
439                }
440                if (ack != null && sub.expectAck(publish)) {
441                    synchronized (consumerAcks) {
442                        consumerAcks.put(publish.messageId(), ack);
443                    }
444                }
445                LOG.trace("MQTT Snd PUBLISH message:{} client:{} connection:{}",
446                          publish.messageId(), clientId, connectionInfo.getConnectionId());
447                getMQTTTransport().sendToMQTT(publish.encode());
448                if (ack != null && !sub.expectAck(publish)) {
449                    getMQTTTransport().sendToActiveMQ(ack);
450                }
451            }
452        } else if (command.getDataStructureType() == ConnectionError.DATA_STRUCTURE_TYPE) {
453            // Pass down any unexpected async errors. Should this close the connection?
454            Throwable exception = ((ConnectionError) command).getException();
455            handleException(exception, null);
456        } else if (command.isBrokerInfo()) {
457            //ignore
458        } else {
459            LOG.debug("Do not know how to process ActiveMQ Command {}", command);
460        }
461    }
462
463    void onMQTTPublish(PUBLISH command) throws IOException, JMSException {
464        checkConnected();
465        LOG.trace("MQTT Rcv PUBLISH message:{} client:{} connection:{}",
466                  command.messageId(), clientId, connectionInfo.getConnectionId());
467        ActiveMQMessage message = convertMessage(command);
468        message.setProducerId(producerId);
469        message.onSend();
470        sendToActiveMQ(message, createResponseHandler(command));
471    }
472
473    void onMQTTPubAck(PUBACK command) {
474        short messageId = command.messageId();
475        LOG.trace("MQTT Rcv PUBACK message:{} client:{} connection:{}",
476                  messageId, clientId, connectionInfo.getConnectionId());
477        packetIdGenerator.ackPacketId(getClientId(), messageId);
478        MessageAck ack;
479        synchronized (consumerAcks) {
480            ack = consumerAcks.remove(messageId);
481        }
482        if (ack != null) {
483            getMQTTTransport().sendToActiveMQ(ack);
484        }
485    }
486
487    void onMQTTPubRec(PUBREC commnand) {
488        //from a subscriber - send a PUBREL in response
489        PUBREL pubrel = new PUBREL();
490        pubrel.messageId(commnand.messageId());
491        sendToMQTT(pubrel.encode());
492    }
493
494    void onMQTTPubRel(PUBREL command) {
495        PUBREC ack;
496        synchronized (publisherRecs) {
497            ack = publisherRecs.remove(command.messageId());
498        }
499        if (ack == null) {
500            LOG.warn("Unknown PUBREL: {} received", command.messageId());
501        }
502        PUBCOMP pubcomp = new PUBCOMP();
503        pubcomp.messageId(command.messageId());
504        sendToMQTT(pubcomp.encode());
505    }
506
507    void onMQTTPubComp(PUBCOMP command) {
508        short messageId = command.messageId();
509        packetIdGenerator.ackPacketId(getClientId(), messageId);
510        MessageAck ack;
511        synchronized (consumerAcks) {
512            ack = consumerAcks.remove(messageId);
513        }
514        if (ack != null) {
515            getMQTTTransport().sendToActiveMQ(ack);
516        }
517    }
518
519    ActiveMQMessage convertMessage(PUBLISH command) throws JMSException {
520        ActiveMQBytesMessage msg = new ActiveMQBytesMessage();
521
522        msg.setProducerId(producerId);
523        MessageId id = new MessageId(producerId, publisherIdGenerator.getNextSequenceId());
524        msg.setMessageId(id);
525        LOG.trace("MQTT-->ActiveMQ: MQTT_MSGID:{} client:{} connection:{} ActiveMQ_MSGID:{}",
526                command.messageId(), clientId, connectionInfo.getConnectionId(), msg.getMessageId());
527        msg.setTimestamp(System.currentTimeMillis());
528        msg.setPriority((byte) Message.DEFAULT_PRIORITY);
529        msg.setPersistent(command.qos() != QoS.AT_MOST_ONCE && !command.retain());
530        msg.setIntProperty(QOS_PROPERTY_NAME, command.qos().ordinal());
531        if (command.retain()) {
532            msg.setBooleanProperty(RetainedMessageSubscriptionRecoveryPolicy.RETAIN_PROPERTY, true);
533        }
534
535        ActiveMQDestination destination;
536        synchronized (activeMQDestinationMap) {
537            destination = activeMQDestinationMap.get(command.topicName());
538            if (destination == null) {
539                String topicName = MQTTProtocolSupport.convertMQTTToActiveMQ(command.topicName().toString());
540                try {
541                    destination = findSubscriptionStrategy().onSend(topicName);
542                } catch (IOException e) {
543                    throw JMSExceptionSupport.create(e);
544                }
545
546                activeMQDestinationMap.put(command.topicName().toString(), destination);
547            }
548        }
549
550        msg.setJMSDestination(destination);
551        msg.writeBytes(command.payload().data, command.payload().offset, command.payload().length);
552        return msg;
553    }
554
555    public PUBLISH convertMessage(ActiveMQMessage message) throws IOException, JMSException, DataFormatException {
556        PUBLISH result = new PUBLISH();
557        // packet id is set in MQTTSubscription
558        QoS qoS;
559        if (message.propertyExists(QOS_PROPERTY_NAME)) {
560            int ordinal = message.getIntProperty(QOS_PROPERTY_NAME);
561            qoS = QoS.values()[ordinal];
562
563        } else {
564            qoS = message.isPersistent() ? QoS.AT_MOST_ONCE : QoS.AT_LEAST_ONCE;
565        }
566        result.qos(qoS);
567        if (message.getBooleanProperty(RetainedMessageSubscriptionRecoveryPolicy.RETAINED_PROPERTY)) {
568            result.retain(true);
569        }
570
571        String topicName;
572        synchronized (mqttTopicMap) {
573            topicName = mqttTopicMap.get(message.getJMSDestination());
574            if (topicName == null) {
575                String amqTopicName = findSubscriptionStrategy().onSend(message.getDestination());
576                topicName = MQTTProtocolSupport.convertActiveMQToMQTT(amqTopicName);
577                mqttTopicMap.put(message.getJMSDestination(), topicName);
578            }
579        }
580        result.topicName(new UTF8Buffer(topicName));
581
582        if (message.getDataStructureType() == ActiveMQTextMessage.DATA_STRUCTURE_TYPE) {
583            ActiveMQTextMessage msg = (ActiveMQTextMessage) message.copy();
584            msg.setReadOnlyBody(true);
585            String messageText = msg.getText();
586            if (messageText != null) {
587                result.payload(new Buffer(messageText.getBytes("UTF-8")));
588            }
589        } else if (message.getDataStructureType() == ActiveMQBytesMessage.DATA_STRUCTURE_TYPE) {
590            ActiveMQBytesMessage msg = (ActiveMQBytesMessage) message.copy();
591            msg.setReadOnlyBody(true);
592            byte[] data = new byte[(int) msg.getBodyLength()];
593            msg.readBytes(data);
594            result.payload(new Buffer(data));
595        } else if (message.getDataStructureType() == ActiveMQMapMessage.DATA_STRUCTURE_TYPE) {
596            ActiveMQMapMessage msg = (ActiveMQMapMessage) message.copy();
597            msg.setReadOnlyBody(true);
598            Map<String, Object> map = msg.getContentMap();
599            if (map != null) {
600                result.payload(new Buffer(map.toString().getBytes("UTF-8")));
601            }
602        } else {
603            ByteSequence byteSequence = message.getContent();
604            if (byteSequence != null && byteSequence.getLength() > 0) {
605                if (message.isCompressed()) {
606                    Inflater inflater = new Inflater();
607                    inflater.setInput(byteSequence.data, byteSequence.offset, byteSequence.length);
608                    byte[] data = new byte[4096];
609                    int read;
610                    ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
611                    while ((read = inflater.inflate(data)) != 0) {
612                        bytesOut.write(data, 0, read);
613                    }
614                    byteSequence = bytesOut.toByteSequence();
615                    bytesOut.close();
616                }
617                result.payload(new Buffer(byteSequence.data, byteSequence.offset, byteSequence.length));
618            }
619        }
620        LOG.trace("ActiveMQ-->MQTT:MQTT_MSGID:{} client:{} connection:{} ActiveMQ_MSGID:{}",
621                result.messageId(), clientId, connectionInfo.getConnectionId(), message.getMessageId());
622        return result;
623    }
624
625    public MQTTTransport getMQTTTransport() {
626        return mqttTransport;
627    }
628
629    boolean willSent = false;
630    public void onTransportError() {
631        if (connect != null) {
632            if (connected.get()) {
633                if (connect.willTopic() != null && connect.willMessage() != null && !willSent) {
634                    willSent = true;
635                    try {
636                        PUBLISH publish = new PUBLISH();
637                        publish.topicName(connect.willTopic());
638                        publish.qos(connect.willQos());
639                        publish.messageId(packetIdGenerator.getNextSequenceId(getClientId()));
640                        publish.payload(connect.willMessage());
641                        publish.retain(connect.willRetain());
642                        ActiveMQMessage message = convertMessage(publish);
643                        message.setProducerId(producerId);
644                        message.onSend();
645
646                        sendToActiveMQ(message, null);
647                    } catch (Exception e) {
648                        LOG.warn("Failed to publish Will Message " + connect.willMessage());
649                    }
650                }
651                // remove connection info
652                sendToActiveMQ(connectionInfo.createRemoveCommand(), null);
653            }
654        }
655    }
656
657    void configureInactivityMonitor(short keepAliveSeconds) {
658        MQTTInactivityMonitor monitor = getMQTTTransport().getInactivityMonitor();
659
660        // If the user specifically shuts off the InactivityMonitor with transport.useInactivityMonitor=false,
661        // then ignore configuring it because it won't exist
662        if (monitor == null) {
663            return;
664        }
665
666        // Client has sent a valid CONNECT frame, we can stop the connect checker.
667        monitor.stopConnectChecker();
668
669        long keepAliveMS = keepAliveSeconds * 1000;
670
671        LOG.debug("MQTT Client {} requests heart beat of {} ms", getClientId(), keepAliveMS);
672
673        try {
674            // if we have a default keep-alive value, and the client is trying to turn off keep-alive,
675
676            // we'll observe the server-side configured default value (note, no grace period)
677            if (keepAliveMS == 0 && defaultKeepAlive > 0) {
678                keepAliveMS = defaultKeepAlive;
679            }
680
681            long readGracePeriod = (long) (keepAliveMS * MQTT_KEEP_ALIVE_GRACE_PERIOD);
682
683            monitor.setProtocolConverter(this);
684            monitor.setReadKeepAliveTime(keepAliveMS);
685            monitor.setReadGraceTime(readGracePeriod);
686            monitor.startReadChecker();
687
688            LOG.debug("MQTT Client {} established heart beat of  {} ms ({} ms + {} ms grace period)",
689                      new Object[] { getClientId(), keepAliveMS, keepAliveMS, readGracePeriod });
690        } catch (Exception ex) {
691            LOG.warn("Failed to start MQTT InactivityMonitor ", ex);
692        }
693    }
694
695    void handleException(Throwable exception, MQTTFrame command) {
696        LOG.warn("Exception occurred processing: \n" + command + ": " + exception.toString());
697        LOG.debug("Exception detail", exception);
698
699        if (connected.get() && connectionInfo != null) {
700            connected.set(false);
701            sendToActiveMQ(connectionInfo.createRemoveCommand(), null);
702        }
703        stopTransport();
704    }
705
706    void checkConnected() throws MQTTProtocolException {
707        if (!connected.get()) {
708            throw new MQTTProtocolException("Not connected.");
709        }
710    }
711
712    private void stopTransport() {
713        try {
714            getMQTTTransport().stop();
715        } catch (Throwable e) {
716            LOG.debug("Failed to stop MQTT transport ", e);
717        }
718    }
719
720    ResponseHandler createResponseHandler(final PUBLISH command) {
721        if (command != null) {
722            return new ResponseHandler() {
723                @Override
724                public void onResponse(MQTTProtocolConverter converter, Response response) throws IOException {
725                    if (response.isException()) {
726                        Throwable error = ((ExceptionResponse) response).getException();
727                        LOG.warn("Failed to send MQTT Publish: ", command, error.getMessage());
728                        LOG.trace("Error trace: {}", error);
729                    }
730
731                    switch (command.qos()) {
732                        case AT_LEAST_ONCE:
733                            PUBACK ack = new PUBACK();
734                            ack.messageId(command.messageId());
735                            LOG.trace("MQTT Snd PUBACK message:{} client:{} connection:{}",
736                                      command.messageId(), clientId, connectionInfo.getConnectionId());
737                            converter.getMQTTTransport().sendToMQTT(ack.encode());
738                            break;
739                        case EXACTLY_ONCE:
740                            PUBREC req = new PUBREC();
741                            req.messageId(command.messageId());
742                            synchronized (publisherRecs) {
743                                publisherRecs.put(command.messageId(), req);
744                            }
745                            LOG.trace("MQTT Snd PUBREC message:{} client:{} connection:{}",
746                                      command.messageId(), clientId, connectionInfo.getConnectionId());
747                            converter.getMQTTTransport().sendToMQTT(req.encode());
748                            break;
749                        default:
750                            break;
751                    }
752                }
753            };
754        }
755        return null;
756    }
757
758    public long getDefaultKeepAlive() {
759        return defaultKeepAlive;
760    }
761
762    /**
763     * Set the default keep alive time (in milliseconds) that would be used if configured on server side
764     * and the client sends a keep-alive value of 0 (zero) on a CONNECT frame
765     * @param keepAlive the keepAlive in milliseconds
766     */
767    public void setDefaultKeepAlive(long keepAlive) {
768        this.defaultKeepAlive = keepAlive;
769    }
770
771    public int getActiveMQSubscriptionPrefetch() {
772        return activeMQSubscriptionPrefetch;
773    }
774
775    /**
776     * set the default prefetch size when mapping the MQTT subscription to an ActiveMQ one
777     * The default = 1
778     *
779     * @param activeMQSubscriptionPrefetch
780     *        set the prefetch for the corresponding ActiveMQ subscription
781     */
782    public void setActiveMQSubscriptionPrefetch(int activeMQSubscriptionPrefetch) {
783        this.activeMQSubscriptionPrefetch = activeMQSubscriptionPrefetch;
784    }
785
786    public MQTTPacketIdGenerator getPacketIdGenerator() {
787        return packetIdGenerator;
788    }
789
790    public void setPublishDollarTopics(boolean publishDollarTopics) {
791        this.publishDollarTopics = publishDollarTopics;
792    }
793
794    public boolean getPublishDollarTopics() {
795        return publishDollarTopics;
796    }
797
798    public ConnectionId getConnectionId() {
799        return connectionId;
800    }
801
802    public SessionId getSessionId() {
803        return sessionId;
804    }
805
806    public boolean isCleanSession() {
807        return this.connect.cleanSession();
808    }
809
810    public String getSubscriptionStrategy() {
811        return subscriptionStrategyName;
812    }
813
814    public void setSubscriptionStrategy(String name) {
815        this.subscriptionStrategyName = name;
816    }
817
818    public String getClientId() {
819        if (clientId == null) {
820            if (connect != null && connect.clientId() != null) {
821                clientId = connect.clientId().toString();
822            } else {
823                clientId = "";
824            }
825        }
826        return clientId;
827    }
828
829    protected MQTTSubscriptionStrategy findSubscriptionStrategy() throws IOException {
830        if (subsciptionStrategy == null) {
831            synchronized (STRATAGY_FINDER) {
832                if (subsciptionStrategy != null) {
833                    return subsciptionStrategy;
834                }
835
836                MQTTSubscriptionStrategy strategy = null;
837                if (subscriptionStrategyName != null && !subscriptionStrategyName.isEmpty()) {
838                    try {
839                        strategy = (MQTTSubscriptionStrategy) STRATAGY_FINDER.newInstance(subscriptionStrategyName);
840                        LOG.debug("MQTT Using subscription strategy: {}", subscriptionStrategyName);
841                        if (strategy instanceof BrokerServiceAware) {
842                            ((BrokerServiceAware)strategy).setBrokerService(brokerService);
843                        }
844                        strategy.initialize(this);
845                    } catch (Exception e) {
846                        throw IOExceptionSupport.create(e);
847                    }
848                } else {
849                    throw new IOException("Invalid subscription strategy name given: " + subscriptionStrategyName);
850                }
851
852                this.subsciptionStrategy = strategy;
853            }
854        }
855        return subsciptionStrategy;
856    }
857}