net.dreamlu.iot.mqtt.codec.MqttProperties Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2020 The Netty Project
*
* The Netty Project 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:
*
* https://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 net.dreamlu.iot.mqtt.codec;
import java.util.*;
/**
* MQTT Properties container
*
* @author netty
*/
public final class MqttProperties {
public enum MqttPropertyType {
// single byte properties
/**
* 有效载荷标识(Payload Format Indicator),该属性只存在于 PUBLISH 报文和 CONNECT 报文的遗嘱属性中。
*/
PAYLOAD_FORMAT_INDICATOR((byte) 0x01),
/**
* 请求问题信息
*/
REQUEST_PROBLEM_INFORMATION((byte) 0x17),
/**
* 请求响应信息
*/
REQUEST_RESPONSE_INFORMATION((byte) 0x19),
/**
* 服务器支持得最高 qos 级别
*/
MAXIMUM_QOS((byte) 0x24),
/**
* 保留消息可用
*/
RETAIN_AVAILABLE((byte) 0x25),
/**
* 订阅通配符可用
*/
WILDCARD_SUBSCRIPTION_AVAILABLE((byte) 0x28),
/**
* 订阅标识符可用
*/
SUBSCRIPTION_IDENTIFIER_AVAILABLE((byte) 0x29),
/**
* $share 共享订阅可用
*/
SHARED_SUBSCRIPTION_AVAILABLE((byte) 0x2A),
// two bytes properties
/**
* 服务器 keep alive
*/
SERVER_KEEP_ALIVE((byte) 0x13),
/**
* 告知对方自己希望处理未决的最大的 Qos1 或者 Qos2 PUBLISH消息个数,如果不存在,则默认是65535。作用:流控。
*/
RECEIVE_MAXIMUM((byte) 0x21),
/**
* topic 别名最大值
*/
TOPIC_ALIAS_MAXIMUM((byte) 0x22),
/**
* topic 别名
*/
TOPIC_ALIAS((byte) 0x23),
// four bytes properties
PUBLICATION_EXPIRY_INTERVAL((byte) 0x02),
/**
* session 超时时间,连接时使用
*/
SESSION_EXPIRY_INTERVAL((byte) 0x11),
/**
* 遗嘱消息延迟时间
*/
WILL_DELAY_INTERVAL((byte) 0x18),
/**
* 最大包体大小
*/
MAXIMUM_PACKET_SIZE((byte) 0x27),
// Variable Byte Integer
/**
* 订阅标识符
*/
SUBSCRIPTION_IDENTIFIER((byte) 0x0B),
// UTF-8 Encoded String properties
/**
* 内容类型(Content Type),只存在于 PUBLISH 报文和 CONNECT 报文的遗嘱属性中。
* 例如:存放 MIME 类型,比如 text/plain 表示文本文件,audio/aac 表示音频文件。
*/
CONTENT_TYPE((byte) 0x03),
/**
* 响应的 topic
*/
RESPONSE_TOPIC((byte) 0x08),
/**
* 指定的客户标识符
*/
ASSIGNED_CLIENT_IDENTIFIER((byte) 0x12),
/**
* 身份验证方法
*/
AUTHENTICATION_METHOD((byte) 0x15),
/**
* 响应信息
*/
RESPONSE_INFORMATION((byte) 0x1A),
/**
* 服务器参考
*/
SERVER_REFERENCE((byte) 0x1C),
/**
* 所有的ACK以及DISCONNECT 都可以携带 Reason String属性告知对方一些特殊的信息,
* 一般来说是ACK失败的情况下会使用该属性告知对端为什么失败,可用来弥补Reason Code信息不够。
*/
REASON_STRING((byte) 0x1F),
/**
* 用户属性
*/
USER_PROPERTY((byte) 0x26),
// Binary Data
/**
* 相关数据
*/
CORRELATION_DATA((byte) 0x09),
/**
* 认证数据
*/
AUTHENTICATION_DATA((byte) 0x16);
private static final MqttPropertyType[] VALUES;
static {
VALUES = new MqttPropertyType[43];
for (MqttPropertyType v : values()) {
VALUES[v.value] = v;
}
}
private final byte value;
MqttPropertyType(byte value) {
this.value = value;
}
public byte value() {
return value;
}
public static MqttPropertyType valueOf(int type) {
MqttPropertyType t = null;
try {
t = VALUES[type];
} catch (ArrayIndexOutOfBoundsException ignored) {
// nop
}
if (t == null) {
throw new IllegalArgumentException("unknown property type: " + type);
}
return t;
}
}
public static final MqttProperties NO_PROPERTIES = new MqttProperties(false);
static MqttProperties withEmptyDefaults(MqttProperties properties) {
if (properties == null) {
return MqttProperties.NO_PROPERTIES;
}
return properties;
}
/**
* MQTT property base class
*
* @param property type
*/
public abstract static class MqttProperty {
final int propertyId;
final T value;
protected MqttProperty(int propertyId, T value) {
this.propertyId = propertyId;
this.value = value;
}
/**
* Get MQTT property ID
*
* @return property ID
*/
public int propertyId() {
return propertyId;
}
/**
* Get MQTT property value
*
* @return property value
*/
public T value() {
return value;
}
@Override
public int hashCode() {
return propertyId + 31 * value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
MqttProperty that = (MqttProperty) obj;
return this.propertyId == that.propertyId && this.value.equals(that.value);
}
}
public static final class IntegerProperty extends MqttProperty {
public IntegerProperty(MqttPropertyType propertyType, Integer value) {
super(propertyType.value, value);
}
public IntegerProperty(int propertyId, Integer value) {
super(propertyId, value);
}
@Override
public String toString() {
return "IntegerProperty(" + propertyId + ", " + value + ')';
}
}
public static final class StringProperty extends MqttProperty {
public StringProperty(int propertyId, String value) {
super(propertyId, value);
}
@Override
public String toString() {
return "StringProperty(" + propertyId + ", " + value + ')';
}
}
public static final class StringPair {
public final String key;
public final String value;
public StringPair(String key, String value) {
this.key = key;
this.value = value;
}
@Override
public int hashCode() {
return key.hashCode() + 31 * value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
StringPair that = (StringPair) obj;
return that.key.equals(this.key) && that.value.equals(this.value);
}
}
//User properties are the only properties that may be included multiple times and
//are the only properties where ordering is required. Therefore, they need a special handling
public static final class UserProperties extends MqttProperty> {
public UserProperties() {
super(MqttPropertyType.USER_PROPERTY.value, new ArrayList<>());
}
/**
* Create user properties from the collection of the String pair values
*
* @param values string pairs. Collection entries are copied, collection itself isn't shared
*/
public UserProperties(Collection values) {
this();
this.value.addAll(values);
}
private static UserProperties fromUserPropertyCollection(Collection properties) {
UserProperties userProperties = new UserProperties();
for (UserProperty property : properties) {
userProperties.add(new StringPair(property.value.key, property.value.value));
}
return userProperties;
}
public void add(StringPair pair) {
this.value.add(pair);
}
public void add(String key, String value) {
this.value.add(new StringPair(key, value));
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("UserProperties(");
boolean first = true;
for (StringPair pair : value) {
if (!first) {
builder.append(", ");
}
builder.append(pair.key).append("->").append(pair.value);
first = false;
}
builder.append(')');
return builder.toString();
}
}
public static final class UserProperty extends MqttProperty {
public UserProperty(String key, String value) {
super(MqttPropertyType.USER_PROPERTY.value, new StringPair(key, value));
}
@Override
public String toString() {
return "UserProperty(" + value.key + ", " + value.value + ')';
}
}
public static final class BinaryProperty extends MqttProperty {
public BinaryProperty(int propertyId, byte[] value) {
super(propertyId, value);
}
@Override
public String toString() {
return "BinaryProperty(" + propertyId + ", " + value.length + " bytes)";
}
}
public MqttProperties() {
this(true);
}
private MqttProperties(boolean canModify) {
this.canModify = canModify;
}
private Map props;
private List userProperties;
private List subscriptionIds;
private final boolean canModify;
public void add(MqttProperty property) {
if (!canModify) {
throw new UnsupportedOperationException("adding property isn't allowed");
}
Map props = this.props;
if (property.propertyId == MqttPropertyType.USER_PROPERTY.value) {
List userProperties = this.userProperties;
if (userProperties == null) {
userProperties = new ArrayList<>(1);
this.userProperties = userProperties;
}
if (property instanceof UserProperty) {
userProperties.add((UserProperty) property);
} else if (property instanceof UserProperties) {
for (StringPair pair : ((UserProperties) property).value) {
userProperties.add(new UserProperty(pair.key, pair.value));
}
} else {
throw new IllegalArgumentException("User property must be of UserProperty or UserProperties type");
}
} else if (property.propertyId == MqttPropertyType.SUBSCRIPTION_IDENTIFIER.value) {
List subscriptionIds = this.subscriptionIds;
if (subscriptionIds == null) {
subscriptionIds = new ArrayList<>(1);
this.subscriptionIds = subscriptionIds;
}
if (property instanceof IntegerProperty) {
subscriptionIds.add((IntegerProperty) property);
} else {
throw new IllegalArgumentException("Subscription ID must be an integer property");
}
} else {
if (props == null) {
props = new HashMap<>();
this.props = props;
}
props.put(property.propertyId, property);
}
}
public Collection extends MqttProperty> listAll() {
Map props = this.props;
if (props == null && subscriptionIds == null && userProperties == null) {
return Collections.emptyList();
}
if (props == null && userProperties == null) {
return subscriptionIds;
}
if (props == null && subscriptionIds == null) {
return userProperties;
}
if (subscriptionIds == null && userProperties == null) {
return props.values();
}
List propValues = new ArrayList<>(props != null ? props.size() : 1);
if (props != null) {
propValues.addAll(props.values());
}
if (subscriptionIds != null) {
propValues.addAll(subscriptionIds);
}
if (userProperties != null) {
propValues.add(UserProperties.fromUserPropertyCollection(userProperties));
}
return propValues;
}
public boolean isEmpty() {
Map props = this.props;
return props == null || props.isEmpty();
}
/**
* Get property by ID. If there are multiple properties of this type (can be with Subscription ID)
* then return the first one.
*
* @param propertyId ID of the property
* @return a property if it is set, null otherwise
*/
public MqttProperty getProperty(int propertyId) {
if (MqttPropertyType.USER_PROPERTY.value == propertyId) {
//special handling to keep compatibility with earlier versions
List userProperties = this.userProperties;
if (userProperties == null) {
return null;
}
return UserProperties.fromUserPropertyCollection(userProperties);
}
if (MqttPropertyType.SUBSCRIPTION_IDENTIFIER.value == propertyId) {
List subscriptionIds = this.subscriptionIds;
if (subscriptionIds == null || subscriptionIds.isEmpty()) {
return null;
}
return subscriptionIds.get(0);
}
Map props = this.props;
return props == null ? null : props.get(propertyId);
}
/**
* Get property by ID. If there are multiple properties of this type (can be with Subscription ID)
* then return the first one.
*
* @param mqttPropertyType Type of the property
* @return a property if it is set, null otherwise
*/
public MqttProperty getProperty(MqttPropertyType mqttPropertyType) {
return getProperty(mqttPropertyType.value);
}
/**
* Get property by ID. If there are multiple properties of this type (can be with Subscription ID)
* then return the first one.
*
* @param mqttPropertyType Type of the property
* @param 泛型标记
* @return a property value if it is set, null otherwise
*/
public T getPropertyValue(MqttPropertyType mqttPropertyType) {
MqttProperty property = getProperty(mqttPropertyType.value);
if (property == null) {
return null;
}
return (T) property.value();
}
/**
* Get properties by ID.
* Some properties (Subscription ID and User Properties) may occur multiple times,
* this method returns all their values in order.
*
* @param propertyId ID of the property
* @return all properties having specified ID
*/
public List extends MqttProperty> getProperties(int propertyId) {
if (propertyId == MqttPropertyType.USER_PROPERTY.value) {
return userProperties == null ? Collections.emptyList() : userProperties;
}
if (propertyId == MqttPropertyType.SUBSCRIPTION_IDENTIFIER.value) {
return subscriptionIds == null ? Collections.emptyList() : subscriptionIds;
}
Map props = this.props;
return (props == null || !props.containsKey(propertyId)) ?
Collections.emptyList() :
Collections.singletonList(props.get(propertyId));
}
}