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.security.cert.X509Certificate; 021import java.util.concurrent.atomic.AtomicBoolean; 022 023import javax.jms.JMSException; 024 025import org.apache.activemq.broker.BrokerService; 026import org.apache.activemq.command.Command; 027import org.apache.activemq.transport.Transport; 028import org.apache.activemq.transport.TransportFilter; 029import org.apache.activemq.transport.TransportListener; 030import org.apache.activemq.transport.tcp.SslTransport; 031import org.apache.activemq.util.IOExceptionSupport; 032import org.apache.activemq.wireformat.WireFormat; 033import org.fusesource.mqtt.codec.CONNACK; 034import org.fusesource.mqtt.codec.CONNECT; 035import org.fusesource.mqtt.codec.DISCONNECT; 036import org.fusesource.mqtt.codec.MQTTFrame; 037import org.fusesource.mqtt.codec.PINGREQ; 038import org.fusesource.mqtt.codec.PINGRESP; 039import org.fusesource.mqtt.codec.PUBACK; 040import org.fusesource.mqtt.codec.PUBCOMP; 041import org.fusesource.mqtt.codec.PUBLISH; 042import org.fusesource.mqtt.codec.PUBREC; 043import org.fusesource.mqtt.codec.PUBREL; 044import org.fusesource.mqtt.codec.SUBACK; 045import org.fusesource.mqtt.codec.SUBSCRIBE; 046import org.fusesource.mqtt.codec.UNSUBSCRIBE; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049 050/** 051 * The MQTTTransportFilter normally sits on top of a TcpTransport that has been 052 * configured with the StompWireFormat and is used to convert MQTT commands to 053 * ActiveMQ commands. All of the conversion work is done by delegating to the 054 * MQTTProtocolConverter 055 */ 056public class MQTTTransportFilter extends TransportFilter implements MQTTTransport { 057 private static final Logger LOG = LoggerFactory.getLogger(MQTTTransportFilter.class); 058 private static final Logger TRACE = LoggerFactory.getLogger(MQTTTransportFilter.class.getPackage().getName() + ".MQTTIO"); 059 private final MQTTProtocolConverter protocolConverter; 060 private MQTTInactivityMonitor monitor; 061 private MQTTWireFormat wireFormat; 062 private final AtomicBoolean stopped = new AtomicBoolean(); 063 064 private boolean trace; 065 private final Object sendLock = new Object(); 066 067 public MQTTTransportFilter(Transport next, WireFormat wireFormat, BrokerService brokerService) { 068 super(next); 069 this.protocolConverter = new MQTTProtocolConverter(this, brokerService); 070 071 if (wireFormat instanceof MQTTWireFormat) { 072 this.wireFormat = (MQTTWireFormat) wireFormat; 073 } 074 } 075 076 @Override 077 public void oneway(Object o) throws IOException { 078 try { 079 final Command command = (Command) o; 080 protocolConverter.onActiveMQCommand(command); 081 } catch (Exception e) { 082 throw IOExceptionSupport.create(e); 083 } 084 } 085 086 @Override 087 public void onCommand(Object command) { 088 try { 089 MQTTFrame frame = (MQTTFrame) command; 090 if (trace) { 091 TRACE.trace("Received: " + toString(frame)); 092 } 093 protocolConverter.onMQTTCommand(frame); 094 } catch (IOException e) { 095 onException(e); 096 } catch (JMSException e) { 097 onException(IOExceptionSupport.create(e)); 098 } 099 } 100 101 @Override 102 public void sendToActiveMQ(Command command) { 103 TransportListener l = transportListener; 104 if (l != null) { 105 l.onCommand(command); 106 } 107 } 108 109 @Override 110 public void sendToMQTT(MQTTFrame command) throws IOException { 111 if( !stopped.get() ) { 112 if (trace) { 113 TRACE.trace("Sending : " + toString(command)); 114 } 115 Transport n = next; 116 if (n != null) { 117 // sync access to underlying transport buffer 118 synchronized (sendLock) { 119 n.oneway(command); 120 } 121 } 122 } 123 } 124 125 static private String toString(MQTTFrame frame) { 126 if( frame == null ) 127 return null; 128 try { 129 switch (frame.messageType()) { 130 case PINGREQ.TYPE: return new PINGREQ().decode(frame).toString(); 131 case PINGRESP.TYPE: return new PINGRESP().decode(frame).toString(); 132 case CONNECT.TYPE: return new CONNECT().decode(frame).toString(); 133 case DISCONNECT.TYPE: return new DISCONNECT().decode(frame).toString(); 134 case SUBSCRIBE.TYPE: return new SUBSCRIBE().decode(frame).toString(); 135 case UNSUBSCRIBE.TYPE: return new UNSUBSCRIBE().decode(frame).toString(); 136 case PUBLISH.TYPE: return new PUBLISH().decode(frame).toString(); 137 case PUBACK.TYPE: return new PUBACK().decode(frame).toString(); 138 case PUBREC.TYPE: return new PUBREC().decode(frame).toString(); 139 case PUBREL.TYPE: return new PUBREL().decode(frame).toString(); 140 case PUBCOMP.TYPE: return new PUBCOMP().decode(frame).toString(); 141 case CONNACK.TYPE: return new CONNACK().decode(frame).toString(); 142 case SUBACK.TYPE: return new SUBACK().decode(frame).toString(); 143 default: return frame.toString(); 144 } 145 } catch (Throwable e) { 146 LOG.warn(e.getMessage(), e); 147 return frame.toString(); 148 } 149 } 150 151 @Override 152 public void start() throws Exception { 153 if (monitor != null) { 154 monitor.startConnectChecker(getConnectAttemptTimeout()); 155 } 156 super.start(); 157 } 158 159 @Override 160 public void stop() throws Exception { 161 if (stopped.compareAndSet(false, true)) { 162 super.stop(); 163 } 164 } 165 166 @Override 167 public X509Certificate[] getPeerCertificates() { 168 if (next instanceof SslTransport) { 169 X509Certificate[] peerCerts = ((SslTransport) next).getPeerCertificates(); 170 if (trace && peerCerts != null) { 171 LOG.debug("Peer Identity has been verified\n"); 172 } 173 return peerCerts; 174 } 175 return null; 176 } 177 178 public boolean isTrace() { 179 return trace; 180 } 181 182 public void setTrace(boolean trace) { 183 this.trace = trace; 184 } 185 186 @Override 187 public MQTTInactivityMonitor getInactivityMonitor() { 188 return monitor; 189 } 190 191 public void setInactivityMonitor(MQTTInactivityMonitor monitor) { 192 this.monitor = monitor; 193 } 194 195 @Override 196 public MQTTWireFormat getWireFormat() { 197 return this.wireFormat; 198 } 199 200 @Override 201 public void onException(IOException error) { 202 protocolConverter.onTransportError(); 203 super.onException(error); 204 } 205 206 public long getDefaultKeepAlive() { 207 return protocolConverter != null ? protocolConverter.getDefaultKeepAlive() : -1; 208 } 209 210 public void setDefaultKeepAlive(long defaultHeartBeat) { 211 protocolConverter.setDefaultKeepAlive(defaultHeartBeat); 212 } 213 214 /** 215 * @return the timeout value used to fail a connection if no CONNECT frame read. 216 */ 217 public long getConnectAttemptTimeout() { 218 return wireFormat.getConnectAttemptTimeout(); 219 } 220 221 /** 222 * Sets the timeout value used to fail a connection if no CONNECT frame is read 223 * in the given interval. 224 * 225 * @param connectTimeout 226 * the connection frame received timeout value. 227 */ 228 public void setConnectAttemptTimeout(long connectTimeout) { 229 wireFormat.setConnectAttemptTimeout(connectTimeout); 230 } 231 232 public boolean getPublishDollarTopics() { 233 return protocolConverter != null && protocolConverter.getPublishDollarTopics(); 234 } 235 236 public void setPublishDollarTopics(boolean publishDollarTopics) { 237 protocolConverter.setPublishDollarTopics(publishDollarTopics); 238 } 239 240 public String getSubscriptionStrategy() { 241 return protocolConverter != null ? protocolConverter.getSubscriptionStrategy() : "default"; 242 } 243 244 public void setSubscriptionStrategy(String name) { 245 protocolConverter.setSubscriptionStrategy(name); 246 } 247 248 public int getActiveMQSubscriptionPrefetch() { 249 return protocolConverter.getActiveMQSubscriptionPrefetch(); 250 } 251 252 /** 253 * set the default prefetch size when mapping the MQTT subscription to an ActiveMQ one 254 * The default = 1 255 * @param activeMQSubscriptionPrefetch set the prefetch for the corresponding ActiveMQ subscription 256 */ 257 public void setActiveMQSubscriptionPrefetch(int activeMQSubscriptionPrefetch) { 258 protocolConverter.setActiveMQSubscriptionPrefetch(activeMQSubscriptionPrefetch); 259 } 260 261 /** 262 * @return the maximum number of bytes a single MQTT message frame is allowed to be. 263 */ 264 public int getMaxFrameSize() { 265 return wireFormat.getMaxFrameSize(); 266 } 267 268 /** 269 * Sets the maximum frame size for an incoming MQTT frame. The protocl limit is 270 * 256 megabytes and this value cannot be set higher. 271 * 272 * @param maxFrameSize 273 * the maximum allowed frame size for a single MQTT frame. 274 */ 275 public void setMaxFrameSize(int maxFrameSize) { 276 wireFormat.setMaxFrameSize(maxFrameSize); 277 } 278}