37 changed files with 21987 additions and 15 deletions
@ -0,0 +1,288 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.session; |
|||
|
|||
import com.google.common.util.concurrent.FutureCallback; |
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import com.google.common.util.concurrent.SettableFuture; |
|||
import com.google.gson.JsonElement; |
|||
import com.google.gson.JsonNull; |
|||
import com.google.protobuf.InvalidProtocolBufferException; |
|||
import io.netty.buffer.ByteBuf; |
|||
import io.netty.channel.ChannelFuture; |
|||
import io.netty.channel.ChannelHandlerContext; |
|||
import io.netty.handler.codec.mqtt.MqttMessage; |
|||
import io.netty.handler.codec.mqtt.MqttPublishMessage; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.util.ConcurrentReferenceHashMap; |
|||
import org.thingsboard.server.common.data.StringUtils; |
|||
import org.thingsboard.server.common.transport.TransportService; |
|||
import org.thingsboard.server.common.transport.TransportServiceCallback; |
|||
import org.thingsboard.server.common.transport.adaptor.AdaptorException; |
|||
import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromSparkplugResponse; |
|||
import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; |
|||
import org.thingsboard.server.gen.transport.TransportApiProtos; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; |
|||
import org.thingsboard.server.transport.mqtt.MqttTransportContext; |
|||
import org.thingsboard.server.transport.mqtt.MqttTransportHandler; |
|||
import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor; |
|||
import org.thingsboard.server.transport.mqtt.adaptors.ProtoMqttAdaptor; |
|||
|
|||
import javax.annotation.Nullable; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.locks.Lock; |
|||
import java.util.concurrent.locks.ReentrantLock; |
|||
|
|||
import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED; |
|||
import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_OPEN; |
|||
import static org.thingsboard.server.common.transport.service.DefaultTransportService.SUBSCRIBE_TO_ATTRIBUTE_UPDATES_ASYNC_MSG; |
|||
import static org.thingsboard.server.common.transport.service.DefaultTransportService.SUBSCRIBE_TO_RPC_ASYNC_MSG; |
|||
import static org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugTopicUtil.parseTopic; |
|||
|
|||
/** |
|||
* Created by nickAS21 on 12.12.22 |
|||
*/ |
|||
@Slf4j |
|||
public class SparkplugNodeSessionHandler { |
|||
|
|||
private static final String DEFAULT_DEVICE_TYPE = "default"; |
|||
private static final String CAN_T_PARSE_VALUE = "Can't parse value: "; |
|||
private static final String DEVICE_PROPERTY = "device"; |
|||
|
|||
private final MqttTransportContext context; |
|||
private final TransportService transportService; |
|||
private final TransportDeviceInfo nodeSparkplugInfo; |
|||
private final UUID sessionId; |
|||
private final ConcurrentMap<String, Lock> deviceCreationLockMap; |
|||
private final ConcurrentMap<String, SparkplugSessionCtx> devices = new ConcurrentHashMap<>(); |
|||
private final ConcurrentMap<String, ListenableFuture<SparkplugSessionCtx>> deviceFutures = new ConcurrentHashMap<>(); |
|||
private final ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap; |
|||
private final ChannelHandlerContext channel; |
|||
private final DeviceSessionCtx deviceSessionCtx; |
|||
private String nodeTopic; |
|||
|
|||
public SparkplugNodeSessionHandler(DeviceSessionCtx deviceSessionCtx, UUID sessionId, String nodeTopic) { |
|||
this.context = deviceSessionCtx.getContext(); |
|||
this.transportService = context.getTransportService(); |
|||
this.deviceSessionCtx = deviceSessionCtx; |
|||
this.nodeSparkplugInfo = deviceSessionCtx.getDeviceInfo(); |
|||
this.sessionId = sessionId; |
|||
this.deviceCreationLockMap = createWeakMap(); |
|||
this.mqttQoSMap = deviceSessionCtx.getMqttQoSMap(); |
|||
this.channel = deviceSessionCtx.getChannel(); |
|||
this.nodeTopic = nodeTopic; |
|||
} |
|||
|
|||
ConcurrentReferenceHashMap<String, Lock> createWeakMap() { |
|||
return new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.WEAK); |
|||
} |
|||
|
|||
public String getNodeId() { |
|||
return context.getNodeId(); |
|||
} |
|||
|
|||
public UUID getSessionId() { |
|||
return sessionId; |
|||
} |
|||
|
|||
public String getNodeTopic() { |
|||
return nodeTopic; |
|||
} |
|||
|
|||
public int nextMsgId() { |
|||
return deviceSessionCtx.nextMsgId(); |
|||
} |
|||
|
|||
public void deregisterSession(String deviceName) { |
|||
SparkplugSessionCtx deviceSessionCtx = devices.remove(deviceName); |
|||
if (deviceSessionCtx != null) { |
|||
deregisterSession(deviceName, deviceSessionCtx); |
|||
} else { |
|||
log.debug("[{}] Device [{}] was already removed from the gateway session", sessionId, deviceName); |
|||
} |
|||
} |
|||
|
|||
private void deregisterSession(String deviceName, SparkplugSessionCtx deviceSessionCtx) { |
|||
transportService.deregisterSession(deviceSessionCtx.getSessionInfo()); |
|||
transportService.process(deviceSessionCtx.getSessionInfo(), SESSION_EVENT_MSG_CLOSED, null); |
|||
log.debug("[{}] Removed device [{}] from the gateway session", sessionId, deviceName); |
|||
} |
|||
|
|||
public void onDeviceDeleted(String deviceName) { |
|||
deregisterSession(deviceName); |
|||
} |
|||
|
|||
private int getMsgId(MqttPublishMessage mqttMsg) { |
|||
return mqttMsg.variableHeader().packetId(); |
|||
} |
|||
|
|||
public void onDeviceConnectProto(MqttPublishMessage mqttMsg) throws AdaptorException { |
|||
try { |
|||
String deviceName = parseTopic(mqttMsg.variableHeader().topicName()).getDeviceId(); |
|||
String deviceType = StringUtils.isEmpty(nodeSparkplugInfo.getDeviceType()) ? DEFAULT_DEVICE_TYPE : nodeSparkplugInfo.getDeviceType(); |
|||
processOnConnect(mqttMsg, deviceName, deviceType); |
|||
} catch (Exception e) { |
|||
throw new AdaptorException(e); |
|||
} |
|||
} |
|||
|
|||
private void onDeviceDisconnectProto(MqttPublishMessage mqttMsg) throws AdaptorException { |
|||
try { |
|||
TransportApiProtos.DisconnectMsg connectProto = TransportApiProtos.DisconnectMsg.parseFrom(getBytes(mqttMsg.payload())); |
|||
String deviceName = checkDeviceName(connectProto.getDeviceName()); |
|||
processOnDisconnect(mqttMsg, deviceName); |
|||
} catch (RuntimeException | InvalidProtocolBufferException e) { |
|||
throw new AdaptorException(e); |
|||
} |
|||
} |
|||
|
|||
private void processOnDisconnect(MqttPublishMessage msg, String deviceName) { |
|||
deregisterSession(deviceName); |
|||
ack(msg); |
|||
} |
|||
|
|||
|
|||
private JsonElement getJson(MqttPublishMessage mqttMsg) throws AdaptorException { |
|||
return JsonMqttAdaptor.validateJsonPayload(sessionId, mqttMsg.payload()); |
|||
} |
|||
|
|||
private byte[] getBytes(ByteBuf payload) { |
|||
return ProtoMqttAdaptor.toBytes(payload); |
|||
} |
|||
|
|||
private void ack(MqttPublishMessage msg) { |
|||
int msgId = getMsgId(msg); |
|||
if (msgId > 0) { |
|||
writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(msgId)); |
|||
} |
|||
} |
|||
|
|||
ChannelFuture writeAndFlush(MqttMessage mqttMessage) { |
|||
return channel.writeAndFlush(mqttMessage); |
|||
} |
|||
|
|||
private String checkDeviceName(String deviceName) { |
|||
if (StringUtils.isEmpty(deviceName)) { |
|||
throw new RuntimeException("Device name is empty!"); |
|||
} else { |
|||
return deviceName; |
|||
} |
|||
} |
|||
|
|||
private String getDeviceName(JsonElement json) { |
|||
return json.getAsJsonObject().get(DEVICE_PROPERTY).getAsString(); |
|||
} |
|||
|
|||
|
|||
private String getDeviceType(JsonElement json) { |
|||
JsonElement type = json.getAsJsonObject().get("type"); |
|||
return type == null || type instanceof JsonNull ? DEFAULT_DEVICE_TYPE : type.getAsString(); |
|||
} |
|||
|
|||
|
|||
private void processOnConnect(MqttPublishMessage msg, String deviceName, String deviceType) { |
|||
log.trace("[{}] onDeviceConnect: {}", sessionId, deviceName); |
|||
Futures.addCallback(onDeviceConnect(deviceName, deviceType), new FutureCallback<>() { |
|||
@Override |
|||
public void onSuccess(@Nullable SparkplugSessionCtx result) { |
|||
ack(msg); |
|||
log.trace("[{}] onDeviceConnectOk: {}", sessionId, deviceName); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(Throwable t) { |
|||
log.warn("[{}] Failed to process device connect command: {}", sessionId, deviceName, t); |
|||
|
|||
} |
|||
}, context.getExecutor()); |
|||
} |
|||
|
|||
|
|||
private ListenableFuture<SparkplugSessionCtx> onDeviceConnect(String deviceName, String deviceType) { |
|||
SparkplugSessionCtx result = devices.get(deviceName); |
|||
if (result == null) { |
|||
Lock deviceCreationLock = deviceCreationLockMap.computeIfAbsent(deviceName, s -> new ReentrantLock()); |
|||
deviceCreationLock.lock(); |
|||
try { |
|||
result = devices.get(deviceName); |
|||
if (result == null) { |
|||
return getDeviceCreationFuture(deviceName, deviceType); |
|||
} else { |
|||
return Futures.immediateFuture(result); |
|||
} |
|||
} finally { |
|||
deviceCreationLock.unlock(); |
|||
} |
|||
} else { |
|||
return Futures.immediateFuture(result); |
|||
} |
|||
} |
|||
|
|||
private ListenableFuture<SparkplugSessionCtx> getDeviceCreationFuture(String deviceName, String deviceType) { |
|||
final SettableFuture<SparkplugSessionCtx> futureToSet = SettableFuture.create(); |
|||
ListenableFuture<SparkplugSessionCtx> future = deviceFutures.putIfAbsent(deviceName, futureToSet); |
|||
if (future != null) { |
|||
return future; |
|||
} |
|||
try { |
|||
transportService.process(TransportProtos.GetOrCreateDeviceFromSparkplugRequestMsg.newBuilder() |
|||
.setDeviceName(deviceName) |
|||
.setDeviceType(deviceType) |
|||
.setSparkplugIdMSB(nodeSparkplugInfo.getDeviceId().getId().getMostSignificantBits()) |
|||
.setSparkplugIdLSB(nodeSparkplugInfo.getDeviceId().getId().getLeastSignificantBits()) |
|||
.build(), |
|||
new TransportServiceCallback<>() { |
|||
@Override |
|||
public void onSuccess(GetOrCreateDeviceFromSparkplugResponse msg) { |
|||
if (msg.getDeviceInfo() == null) { |
|||
System.out.println("DeviceInfo == null"); |
|||
} |
|||
SparkplugSessionCtx nodeSparkplugSessionCtx = new SparkplugSessionCtx(SparkplugNodeSessionHandler.this, msg.getDeviceInfo(), msg.getDeviceProfile(), mqttQoSMap, transportService); |
|||
if (devices.putIfAbsent(deviceName, nodeSparkplugSessionCtx) == null) { |
|||
log.trace("[{}] First got or created device [{}], type [{}] for the gateway session", sessionId, deviceName, deviceType); |
|||
SessionInfoProto deviceSessionInfo = nodeSparkplugSessionCtx.getSessionInfo(); |
|||
transportService.registerAsyncSession(deviceSessionInfo, nodeSparkplugSessionCtx); |
|||
transportService.process(TransportProtos.TransportToDeviceActorMsg.newBuilder() |
|||
.setSessionInfo(deviceSessionInfo) |
|||
.setSessionEvent(SESSION_EVENT_MSG_OPEN) |
|||
.setSubscribeToAttributes(SUBSCRIBE_TO_ATTRIBUTE_UPDATES_ASYNC_MSG) |
|||
.setSubscribeToRPC(SUBSCRIBE_TO_RPC_ASYNC_MSG) |
|||
.build(), null); |
|||
} |
|||
futureToSet.set(devices.get(deviceName)); |
|||
deviceFutures.remove(deviceName); |
|||
} |
|||
|
|||
@Override |
|||
public void onError(Throwable e) { |
|||
log.warn("[{}] Failed to process device connect command: {}", sessionId, deviceName, e); |
|||
futureToSet.setException(e); |
|||
deviceFutures.remove(deviceName); |
|||
} |
|||
}); |
|||
return futureToSet; |
|||
} catch (Throwable e) { |
|||
deviceFutures.remove(deviceName); |
|||
throw e; |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,175 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore; |
|||
import com.fasterxml.jackson.annotation.JsonInclude; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Metric; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
import java.util.Collection; |
|||
import java.util.Date; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* Created by nickAS21 on 13.12.22 |
|||
*/ |
|||
@JsonInclude(JsonInclude.Include.NON_NULL) |
|||
public class SparkplugBPayload { |
|||
|
|||
private Date timestamp; |
|||
private List<Metric> metrics; |
|||
private long seq = -1; |
|||
private String uuid; |
|||
private byte[] body; |
|||
|
|||
public SparkplugBPayload() {}; |
|||
|
|||
public SparkplugBPayload(Date timestamp, List<Metric> metrics, long seq, String uuid, byte[] body) { |
|||
this.timestamp = timestamp; |
|||
this.metrics = metrics; |
|||
this.seq = seq; |
|||
this.uuid = uuid; |
|||
this.body = body; |
|||
} |
|||
|
|||
public Date getTimestamp() { |
|||
return timestamp; |
|||
} |
|||
|
|||
public void setTimestamp(Date timestamp) { |
|||
this.timestamp = timestamp; |
|||
} |
|||
|
|||
public void addMetric(Metric metric) { |
|||
metrics.add(metric); |
|||
} |
|||
|
|||
public void addMetric(int index, Metric metric) { |
|||
metrics.add(index, metric); |
|||
} |
|||
|
|||
public void addMetrics(List<Metric> metrics) { |
|||
this.metrics.addAll(metrics); |
|||
} |
|||
|
|||
public Metric removeMetric(int index) { |
|||
return metrics.remove(index); |
|||
} |
|||
|
|||
public boolean removeMetric(Metric metric) { |
|||
return metrics.remove(metric); |
|||
} |
|||
|
|||
public List<Metric> getMetrics() { |
|||
return metrics; |
|||
} |
|||
|
|||
@JsonIgnore |
|||
public Integer getMetricCount() { |
|||
return metrics.size(); |
|||
} |
|||
|
|||
public void setMetrics(List<Metric> metrics) { |
|||
this.metrics = metrics; |
|||
} |
|||
|
|||
public long getSeq() { |
|||
return seq; |
|||
} |
|||
|
|||
public void setSeq(long seq) { |
|||
this.seq = seq; |
|||
} |
|||
|
|||
public String getUuid() { |
|||
return uuid; |
|||
} |
|||
|
|||
public void setUuid(String uuid) { |
|||
this.uuid = uuid; |
|||
} |
|||
|
|||
public byte[] getBody() { |
|||
return body; |
|||
} |
|||
|
|||
public void setBody(byte[] body) { |
|||
this.body = body; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "SparkplugBPayload [timestamp=" + timestamp + ", metrics=" + metrics + ", seq=" + seq + ", uuid=" + uuid |
|||
+ ", body=" + Arrays.toString(body) + "]"; |
|||
} |
|||
|
|||
/** |
|||
* A builder for creating a {@link SparkplugBPayload} instance. |
|||
*/ |
|||
public static class SparkplugBPayloadBuilder { |
|||
|
|||
private Date timestamp; |
|||
private List<Metric> metrics; |
|||
private long seq = -1; |
|||
private String uuid; |
|||
private byte[] body; |
|||
|
|||
public SparkplugBPayloadBuilder(long sequenceNumber) { |
|||
this.seq = sequenceNumber; |
|||
metrics = new ArrayList<Metric>(); |
|||
} |
|||
|
|||
public SparkplugBPayloadBuilder() { |
|||
metrics = new ArrayList<Metric>(); |
|||
} |
|||
|
|||
public SparkplugBPayloadBuilder addMetric(Metric metric) { |
|||
this.metrics.add(metric); |
|||
return this; |
|||
} |
|||
|
|||
public SparkplugBPayloadBuilder addMetrics(Collection<Metric> metrics) { |
|||
this.metrics.addAll(metrics); |
|||
return this; |
|||
} |
|||
|
|||
public SparkplugBPayloadBuilder setTimestamp(Date timestamp) { |
|||
this.timestamp = timestamp; |
|||
return this; |
|||
} |
|||
|
|||
public SparkplugBPayloadBuilder setSeq(long seq) { |
|||
this.seq = seq; |
|||
return this; |
|||
} |
|||
|
|||
public SparkplugBPayloadBuilder setUuid(String uuid) { |
|||
this.uuid = uuid; |
|||
return this; |
|||
} |
|||
|
|||
public SparkplugBPayloadBuilder setBody(byte[] body) { |
|||
this.body = body; |
|||
return this; |
|||
} |
|||
|
|||
public SparkplugBPayload createPayload() { |
|||
return new SparkplugBPayload(timestamp, metrics, seq, uuid, body); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,371 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug; |
|||
|
|||
/** |
|||
* Created by nickAS21 on 13.12.22 |
|||
*/ |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.DataSet; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.DataSetDataType; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.File; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.MetaData; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Metric; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.MetricDataType; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Parameter; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.ParameterDataType; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.PropertyDataType; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.PropertySet; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.PropertyValue; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Row; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Template; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.SparkplugValue; |
|||
|
|||
import java.math.BigInteger; |
|||
import java.util.ArrayList; |
|||
import java.util.Collection; |
|||
import java.util.Date; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* A {@link SparkplugPayloadDecoder} implementation for decoding Sparkplug B payloads. |
|||
*/ |
|||
@Slf4j |
|||
public class SparkplugBPayloadDecoder implements SparkplugPayloadDecoder<SparkplugBPayload> { |
|||
|
|||
|
|||
public SparkplugBPayloadDecoder() { |
|||
super(); |
|||
} |
|||
|
|||
public SparkplugBPayload buildFromByteArray(byte[] bytes) throws Exception { |
|||
SparkplugBProto.Payload protoPayload = SparkplugBProto.Payload.parseFrom(bytes); |
|||
SparkplugBPayload.SparkplugBPayloadBuilder builder = new SparkplugBPayload.SparkplugBPayloadBuilder(protoPayload.getSeq()); |
|||
|
|||
// Set the timestamp
|
|||
if (protoPayload.hasTimestamp()) { |
|||
builder.setTimestamp(new Date(protoPayload.getTimestamp())); |
|||
} |
|||
|
|||
// Set the sequence number
|
|||
if (protoPayload.hasSeq()) { |
|||
builder.setSeq(protoPayload.getSeq()); |
|||
} |
|||
|
|||
// Set the Metrics
|
|||
for (SparkplugBProto.Payload.Metric protoMetric : protoPayload.getMetricsList()) { |
|||
builder.addMetric(convertMetric(protoMetric)); |
|||
} |
|||
|
|||
// Set the body
|
|||
if (protoPayload.hasBody()) { |
|||
builder.setBody(protoPayload.getBody().toByteArray()); |
|||
} |
|||
|
|||
// Set the body
|
|||
if (protoPayload.hasUuid()) { |
|||
builder.setUuid(protoPayload.getUuid()); |
|||
} |
|||
|
|||
return builder.createPayload(); |
|||
} |
|||
|
|||
private Metric convertMetric(SparkplugBProto.Payload.Metric protoMetric) throws Exception { |
|||
// Convert the dataType
|
|||
MetricDataType dataType = MetricDataType.fromInteger((protoMetric.getDatatype())); |
|||
|
|||
// Build and return the Metric
|
|||
return new Metric.MetricBuilder(protoMetric.getName(), dataType, getMetricValue(protoMetric)) |
|||
.isHistorical( |
|||
protoMetric.hasIsHistorical() ? protoMetric.getIsHistorical() : null) |
|||
.isTransient(protoMetric |
|||
.hasIsTransient() ? protoMetric.getIsTransient() : null) |
|||
.timestamp(protoMetric.hasTimestamp() ? new Date(protoMetric.getTimestamp()) : null) |
|||
.alias(protoMetric.hasAlias() ? protoMetric.getAlias() : null) |
|||
.metaData(protoMetric.hasMetadata() |
|||
? new MetaData.MetaDataBuilder().contentType(protoMetric.getMetadata().getContentType()) |
|||
.size(protoMetric.getMetadata().getSize()).seq(protoMetric.getMetadata().getSeq()) |
|||
.fileName(protoMetric.getMetadata().getFileName()) |
|||
.fileType(protoMetric.getMetadata().getFileType()) |
|||
.md5(protoMetric.getMetadata().getMd5()) |
|||
.description(protoMetric.getMetadata().getDescription()).createMetaData() |
|||
: null) |
|||
.properties(protoMetric.hasProperties() |
|||
? new PropertySet.PropertySetBuilder().addProperties(convertProperties(protoMetric.getProperties())) |
|||
.createPropertySet() |
|||
: null) |
|||
.createMetric(); |
|||
} |
|||
|
|||
private Map<String, PropertyValue> convertProperties(SparkplugBProto.Payload.PropertySet decodedPropSet) |
|||
throws Exception { |
|||
Map<String, PropertyValue> map = new HashMap<String, PropertyValue>(); |
|||
List<String> keys = decodedPropSet.getKeysList(); |
|||
List<SparkplugBProto.Payload.PropertyValue> values = decodedPropSet.getValuesList(); |
|||
for (int i = 0; i < keys.size(); i++) { |
|||
SparkplugBProto.Payload.PropertyValue value = values.get(i); |
|||
map.put(keys.get(i), |
|||
new PropertyValue(PropertyDataType.fromInteger(value.getType()), getPropertyValue(value))); |
|||
} |
|||
return map; |
|||
} |
|||
|
|||
private Object getPropertyValue(SparkplugBProto.Payload.PropertyValue value) throws Exception { |
|||
PropertyDataType type = PropertyDataType.fromInteger(value.getType()); |
|||
if (value.getIsNull()) { |
|||
return null; |
|||
} |
|||
switch (type) { |
|||
case Boolean: |
|||
return value.getBooleanValue(); |
|||
case DateTime: |
|||
return new Date(value.getLongValue()); |
|||
case Float: |
|||
return value.getFloatValue(); |
|||
case Double: |
|||
return value.getDoubleValue(); |
|||
case Int8: |
|||
return (byte) value.getIntValue(); |
|||
case Int16: |
|||
case UInt8: |
|||
return (short) value.getIntValue(); |
|||
case Int32: |
|||
case UInt16: |
|||
return value.getIntValue(); |
|||
case UInt32: |
|||
case Int64: |
|||
return value.getLongValue(); |
|||
case UInt64: |
|||
return BigInteger.valueOf(value.getLongValue()); |
|||
case String: |
|||
case Text: |
|||
return value.getStringValue(); |
|||
case PropertySet: |
|||
return new PropertySet.PropertySetBuilder().addProperties(convertProperties(value.getPropertysetValue())) |
|||
.createPropertySet(); |
|||
case PropertySetList: |
|||
List<PropertySet> propertySetList = new ArrayList<PropertySet>(); |
|||
List<SparkplugBProto.Payload.PropertySet> list = value.getPropertysetsValue().getPropertysetList(); |
|||
for (SparkplugBProto.Payload.PropertySet decodedPropSet : list) { |
|||
propertySetList.add(new PropertySet.PropertySetBuilder().addProperties(convertProperties(decodedPropSet)) |
|||
.createPropertySet()); |
|||
} |
|||
return propertySetList; |
|||
case Unknown: |
|||
default: |
|||
throw new Exception("Failed to decode: Unknown Property Data Type " + type); |
|||
} |
|||
} |
|||
|
|||
private Object getMetricValue(SparkplugBProto.Payload.Metric protoMetric) throws Exception { |
|||
// Check if the null flag has been set indicating that the value is null
|
|||
if (protoMetric.getIsNull()) { |
|||
return null; |
|||
} |
|||
// Otherwise convert the value based on the type
|
|||
int metricType = protoMetric.getDatatype(); |
|||
switch (MetricDataType.fromInteger(metricType)) { |
|||
case Boolean: |
|||
return protoMetric.getBooleanValue(); |
|||
case DateTime: |
|||
return new Date(protoMetric.getLongValue()); |
|||
case File: |
|||
String filename = protoMetric.getMetadata().getFileName(); |
|||
byte[] fileBytes = protoMetric.getBytesValue().toByteArray(); |
|||
return new File(filename, fileBytes); |
|||
case Float: |
|||
return protoMetric.getFloatValue(); |
|||
case Double: |
|||
return protoMetric.getDoubleValue(); |
|||
case Int8: |
|||
return (byte) protoMetric.getIntValue(); |
|||
case Int16: |
|||
case UInt8: |
|||
return (short) protoMetric.getIntValue(); |
|||
case Int32: |
|||
case UInt16: |
|||
return protoMetric.getIntValue(); |
|||
case UInt32: |
|||
case Int64: |
|||
return protoMetric.getLongValue(); |
|||
case UInt64: |
|||
return BigInteger.valueOf(protoMetric.getLongValue()); |
|||
case String: |
|||
case Text: |
|||
case UUID: |
|||
return protoMetric.getStringValue(); |
|||
case Bytes: |
|||
return protoMetric.getBytesValue().toByteArray(); |
|||
case DataSet: |
|||
SparkplugBProto.Payload.DataSet protoDataSet = protoMetric.getDatasetValue(); |
|||
// Build the and create the DataSet
|
|||
return new DataSet.DataSetBuilder(protoDataSet.getNumOfColumns()).addColumnNames(protoDataSet.getColumnsList()) |
|||
.addTypes(convertDataSetDataTypes(protoDataSet.getTypesList())) |
|||
.addRows(convertDataSetRows(protoDataSet.getRowsList(), protoDataSet.getTypesList())) |
|||
.createDataSet(); |
|||
case Template: |
|||
SparkplugBProto.Payload.Template protoTemplate = protoMetric.getTemplateValue(); |
|||
List<Metric> metrics = new ArrayList<Metric>(); |
|||
List<Parameter> parameters = new ArrayList<Parameter>(); |
|||
|
|||
for (SparkplugBProto.Payload.Template.Parameter protoParameter : protoTemplate.getParametersList()) { |
|||
String name = protoParameter.getName(); |
|||
ParameterDataType type = ParameterDataType.fromInteger(protoParameter.getType()); |
|||
Object value = getParameterValue(protoParameter); |
|||
if (log.isTraceEnabled()) { |
|||
log.trace("Setting template parameter name: " + name + ", type: " + type + ", value: " |
|||
+ value + ", valueType" + value.getClass()); |
|||
} |
|||
|
|||
parameters.add(new Parameter(name, type, value)); |
|||
} |
|||
|
|||
for (SparkplugBProto.Payload.Metric protoTemplateMetric : protoTemplate.getMetricsList()) { |
|||
Metric templateMetric = convertMetric(protoTemplateMetric); |
|||
if (log.isTraceEnabled()) { |
|||
log.trace("Setting template parameter name: " + templateMetric.getName() + ", type: " |
|||
+ templateMetric.getDataType() + ", value: " + templateMetric.getValue()); |
|||
} |
|||
metrics.add(templateMetric); |
|||
} |
|||
|
|||
Template template = new Template.TemplateBuilder().version(protoTemplate.getVersion()) |
|||
.templateRef(protoTemplate.getTemplateRef()).definition(protoTemplate.getIsDefinition()) |
|||
.addMetrics(metrics).addParameters(parameters).createTemplate(); |
|||
|
|||
if (log.isTraceEnabled()) { |
|||
log.trace( |
|||
"Setting template - name: " + protoMetric.getName() + ", version: " + template.getVersion() |
|||
+ ", ref: " + template.getTemplateRef() + ", isDef: " + template.isDefinition() |
|||
+ ", metrics: " + metrics.size() + ", params: " + parameters.size()); |
|||
} |
|||
|
|||
return template; |
|||
case Unknown: |
|||
default: |
|||
throw new Exception("Failed to decode: Unknown Metric DataType " + metricType); |
|||
|
|||
} |
|||
} |
|||
|
|||
private Collection<Row> convertDataSetRows(List<SparkplugBProto.Payload.DataSet.Row> protoRows, |
|||
List<Integer> protoTypes) throws Exception { |
|||
Collection<Row> rows = new ArrayList<Row>(); |
|||
if (protoRows != null) { |
|||
for (SparkplugBProto.Payload.DataSet.Row protoRow : protoRows) { |
|||
List<SparkplugBProto.Payload.DataSet.DataSetValue> protoValues = protoRow.getElementsList(); |
|||
List<SparkplugValue<?>> values = new ArrayList<SparkplugValue<?>>(); |
|||
for (int index = 0; index < protoRow.getElementsCount(); index++) { |
|||
values.add(convertDataSetValue(protoTypes.get(index), protoValues.get(index))); |
|||
} |
|||
// Add the values to the row and the row to the rows
|
|||
rows.add(new Row.RowBuilder().addValues(values).createRow()); |
|||
} |
|||
} |
|||
return rows; |
|||
} |
|||
|
|||
private Collection<DataSetDataType> convertDataSetDataTypes(List<Integer> protoTypes) { |
|||
List<DataSetDataType> types = new ArrayList<DataSetDataType>(); |
|||
// Build up a List of column types
|
|||
for (int type : protoTypes) { |
|||
types.add(DataSetDataType.fromInteger(type)); |
|||
} |
|||
return types; |
|||
} |
|||
|
|||
private Object getParameterValue(SparkplugBProto.Payload.Template.Parameter protoParameter) throws Exception { |
|||
// Otherwise convert the value based on the type
|
|||
int type = protoParameter.getType(); |
|||
switch (MetricDataType.fromInteger(type)) { |
|||
case Boolean: |
|||
return protoParameter.getBooleanValue(); |
|||
case DateTime: |
|||
return new Date(protoParameter.getLongValue()); |
|||
case Float: |
|||
return protoParameter.getFloatValue(); |
|||
case Double: |
|||
return protoParameter.getDoubleValue(); |
|||
case Int8: |
|||
return (byte) protoParameter.getIntValue(); |
|||
case Int16: |
|||
case UInt8: |
|||
return (short) protoParameter.getIntValue(); |
|||
case Int32: |
|||
case UInt16: |
|||
return protoParameter.getIntValue(); |
|||
case UInt32: |
|||
case Int64: |
|||
return protoParameter.getLongValue(); |
|||
case UInt64: |
|||
return BigInteger.valueOf(protoParameter.getLongValue()); |
|||
case String: |
|||
case Text: |
|||
return protoParameter.getStringValue(); |
|||
case Unknown: |
|||
default: |
|||
throw new Exception("Failed to decode: Unknown Parameter Type " + type); |
|||
} |
|||
} |
|||
|
|||
private SparkplugValue<?> convertDataSetValue(int protoType, SparkplugBProto.Payload.DataSet.DataSetValue protoValue) |
|||
throws Exception { |
|||
|
|||
DataSetDataType type = DataSetDataType.fromInteger(protoType); |
|||
switch (type) { |
|||
case Boolean: |
|||
return new SparkplugValue<Boolean>(type, protoValue.getBooleanValue()); |
|||
case DateTime: |
|||
// FIXME - remove after is_null is supported for dataset values
|
|||
if (protoValue.getLongValue() == -9223372036854775808L) { |
|||
return new SparkplugValue<Date>(type, null); |
|||
} else { |
|||
return new SparkplugValue<Date>(type, new Date(protoValue.getLongValue())); |
|||
} |
|||
case Float: |
|||
return new SparkplugValue<Float>(type, protoValue.getFloatValue()); |
|||
case Double: |
|||
return new SparkplugValue<Double>(type, protoValue.getDoubleValue()); |
|||
case Int8: |
|||
return new SparkplugValue<Byte>(type, (byte) protoValue.getIntValue()); |
|||
case UInt8: |
|||
case Int16: |
|||
return new SparkplugValue<Short>(type, (short) protoValue.getIntValue()); |
|||
case UInt16: |
|||
case Int32: |
|||
return new SparkplugValue<Integer>(type, protoValue.getIntValue()); |
|||
case UInt32: |
|||
case Int64: |
|||
return new SparkplugValue<Long>(type, protoValue.getLongValue()); |
|||
case UInt64: |
|||
return new SparkplugValue<BigInteger>(type, BigInteger.valueOf(protoValue.getLongValue())); |
|||
case String: |
|||
case Text: |
|||
if (protoValue.getStringValue().equals("null")) { |
|||
return new SparkplugValue<String>(type, null); |
|||
} else { |
|||
return new SparkplugValue<String>(type, protoValue.getStringValue()); |
|||
} |
|||
case Unknown: |
|||
default: |
|||
log.error("Unknown DataType: " + protoType); |
|||
throw new Exception("Failed to decode"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,545 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug; |
|||
|
|||
import com.google.protobuf.ByteString; |
|||
import org.apache.log4j.LogManager; |
|||
import org.apache.log4j.Logger; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.DataSet; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.DataSetDataType; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.File; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.MetaData; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Metric; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Parameter; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.ParameterDataType; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.PropertyDataType; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.PropertySet; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.PropertyValue; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Row; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Template; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.SparkplugValue; |
|||
|
|||
import java.io.IOException; |
|||
import java.math.BigInteger; |
|||
import java.util.Date; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* Created by nickAS21 on 13.12.22 |
|||
*/ |
|||
public class SparkplugBPayloadEncoder implements SparkplugPayloadEncoder <SparkplugBPayload> { |
|||
|
|||
private static Logger logger = LogManager.getLogger(SparkplugBPayloadEncoder.class.getName()); |
|||
|
|||
public SparkplugBPayloadEncoder() { |
|||
super(); |
|||
} |
|||
|
|||
public byte[] getBytes(SparkplugBPayload payload) throws IOException { |
|||
|
|||
SparkplugBProto.Payload.Builder protoMsg = SparkplugBProto.Payload.newBuilder(); |
|||
|
|||
// Set the timestamp
|
|||
if (payload.getTimestamp() != null) { |
|||
protoMsg.setTimestamp(payload.getTimestamp().getTime()); |
|||
} |
|||
|
|||
// Set the sequence number
|
|||
protoMsg.setSeq(payload.getSeq()); |
|||
|
|||
// Set the UUID if defined
|
|||
if (payload.getUuid() != null) { |
|||
protoMsg.setUuid(payload.getUuid()); |
|||
} |
|||
|
|||
// Set the metrics
|
|||
for (Metric metric : payload.getMetrics()) { |
|||
try { |
|||
protoMsg.addMetrics(convertMetric(metric)); |
|||
} catch(Exception e) { |
|||
logger.error("Failed to add metric: " + metric.getName()); |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
// Set the body
|
|||
if (payload.getBody() != null) { |
|||
protoMsg.setBody(ByteString.copyFrom(payload.getBody())); |
|||
} |
|||
|
|||
return protoMsg.build().toByteArray(); |
|||
} |
|||
|
|||
private SparkplugBProto.Payload.Metric.Builder convertMetric(Metric metric) throws Exception { |
|||
|
|||
// build a metric
|
|||
SparkplugBProto.Payload.Metric.Builder builder = SparkplugBProto.Payload.Metric.newBuilder(); |
|||
|
|||
// set the basic parameters
|
|||
builder.setDatatype(metric.getDataType().toIntValue()); |
|||
builder = setMetricValue(builder, metric); |
|||
|
|||
// Set the name, data type, and value
|
|||
if (metric.hasName()) { |
|||
builder.setName(metric.getName()); |
|||
} |
|||
|
|||
// Set the alias
|
|||
if (metric.hasAlias()) { |
|||
builder.setAlias(metric.getAlias()); |
|||
} |
|||
|
|||
// Set the timestamp
|
|||
if (metric.getTimestamp() != null) { |
|||
builder.setTimestamp(metric.getTimestamp().getTime()); |
|||
} |
|||
|
|||
// Set isHistorical
|
|||
if (metric.getIsHistorical() != null) { |
|||
builder.setIsHistorical(metric.isHistorical()); |
|||
} |
|||
|
|||
// Set isTransient
|
|||
if (metric.getIsTransient() != null) { |
|||
builder.setIsTransient(metric.isTransient()); |
|||
} |
|||
|
|||
// Set isNull
|
|||
if (metric.getIsNull() != null) { |
|||
builder.setIsNull(metric.isNull()); |
|||
} |
|||
|
|||
// Set the metadata
|
|||
if (metric.getMetaData() != null) { |
|||
builder = setMetaData(builder, metric); |
|||
} |
|||
|
|||
// Set the property set
|
|||
if (metric.getProperties() != null) { |
|||
builder.setProperties(convertPropertySet(metric.getProperties())); |
|||
} |
|||
|
|||
return builder; |
|||
} |
|||
|
|||
private SparkplugBProto.Payload.Template.Parameter.Builder convertParameter(Parameter parameter) throws Exception { |
|||
|
|||
// build a metric
|
|||
SparkplugBProto.Payload.Template.Parameter.Builder builder = |
|||
SparkplugBProto.Payload.Template.Parameter.newBuilder(); |
|||
|
|||
if (logger.isTraceEnabled()) { |
|||
logger.trace("Adding parameter: " + parameter.getName()); |
|||
logger.trace(" type: " + parameter.getType()); |
|||
} |
|||
|
|||
// Set the name
|
|||
builder.setName(parameter.getName()); |
|||
|
|||
// Set the type and value
|
|||
builder = setParameterValue(builder, parameter); |
|||
|
|||
return builder; |
|||
} |
|||
|
|||
private SparkplugBProto.Payload.PropertySet.Builder convertPropertySet(PropertySet propertySet) throws Exception { |
|||
SparkplugBProto.Payload.PropertySet.Builder setBuilder = SparkplugBProto.Payload.PropertySet.newBuilder(); |
|||
|
|||
Map<String, PropertyValue> map = propertySet.getPropertyMap(); |
|||
for (String key : map.keySet()) { |
|||
SparkplugBProto.Payload.PropertyValue.Builder builder = SparkplugBProto.Payload.PropertyValue.newBuilder(); |
|||
PropertyValue value = map.get(key); |
|||
PropertyDataType type = value.getType(); |
|||
builder.setType(type.toIntValue()); |
|||
if (value.getValue() == null) { |
|||
builder.setIsNull(true); |
|||
} else { |
|||
switch (type) { |
|||
case Boolean: |
|||
builder.setBooleanValue((Boolean) value.getValue()); |
|||
break; |
|||
case DateTime: |
|||
builder.setLongValue(((Date) value.getValue()).getTime()); |
|||
break; |
|||
case Double: |
|||
builder.setDoubleValue((Double) value.getValue()); |
|||
break; |
|||
case Float: |
|||
builder.setFloatValue((Float) value.getValue()); |
|||
break; |
|||
case Int8: |
|||
builder.setIntValue((Byte) value.getValue()); |
|||
break; |
|||
case Int16: |
|||
case UInt8: |
|||
builder.setIntValue((Short) value.getValue()); |
|||
break; |
|||
case Int32: |
|||
case UInt16: |
|||
builder.setIntValue((Integer) value.getValue()); |
|||
break; |
|||
case Int64: |
|||
case UInt32: |
|||
builder.setLongValue((Long) value.getValue()); |
|||
break; |
|||
case UInt64: |
|||
builder.setLongValue(((BigInteger) value.getValue()).longValue()); |
|||
break; |
|||
case String: |
|||
case Text: |
|||
builder.setStringValue((String) value.getValue()); |
|||
break; |
|||
case PropertySet: |
|||
builder.setPropertysetValue(convertPropertySet((PropertySet) value.getValue())); |
|||
break; |
|||
case PropertySetList: |
|||
List<?> setList = (List<?>) value.getValue(); |
|||
SparkplugBProto.Payload.PropertySetList.Builder listBuilder = |
|||
SparkplugBProto.Payload.PropertySetList.newBuilder(); |
|||
for (Object obj : setList) { |
|||
listBuilder.addPropertyset(convertPropertySet((PropertySet) obj)); |
|||
} |
|||
builder.setPropertysetsValue(listBuilder); |
|||
break; |
|||
case Unknown: |
|||
default: |
|||
logger.error("Unknown DataType: " + value.getType()); |
|||
throw new Exception("Failed to convert value " + value.getType()); |
|||
} |
|||
} |
|||
setBuilder.addKeys(key); |
|||
setBuilder.addValues(builder); |
|||
} |
|||
return setBuilder; |
|||
} |
|||
|
|||
private SparkplugBProto.Payload.Template.Parameter.Builder setParameterValue( |
|||
SparkplugBProto.Payload.Template.Parameter.Builder builder, Parameter parameter) throws Exception { |
|||
ParameterDataType type = parameter.getType(); |
|||
builder.setType(type.toIntValue()); |
|||
Object value = parameter.getValue(); |
|||
switch (type) { |
|||
case Boolean: |
|||
builder.setBooleanValue(toBoolean(value)); |
|||
break; |
|||
case DateTime: |
|||
builder.setLongValue(((Date) value).getTime()); |
|||
break; |
|||
case Double: |
|||
builder.setDoubleValue((Double) value); |
|||
break; |
|||
case Float: |
|||
builder.setFloatValue((Float) value); |
|||
break; |
|||
case Int8: |
|||
builder.setIntValue((Byte) value); |
|||
break; |
|||
case Int16: |
|||
case UInt8: |
|||
builder.setIntValue((Short) value); |
|||
break; |
|||
case Int32: |
|||
case UInt16: |
|||
builder.setIntValue((Integer) value); |
|||
break; |
|||
case Int64: |
|||
case UInt32: |
|||
builder.setLongValue((Long) value); |
|||
break; |
|||
case UInt64: |
|||
builder.setLongValue(((BigInteger) value).longValue()); |
|||
break; |
|||
case Text: |
|||
case String: |
|||
if (value == null) { |
|||
builder.setStringValue(""); |
|||
} else { |
|||
builder.setStringValue((String) value); |
|||
} |
|||
break; |
|||
case Unknown: |
|||
default: |
|||
logger.error("Unknown Type: " + type); |
|||
throw new Exception("Failed to encode"); |
|||
|
|||
} |
|||
return builder; |
|||
} |
|||
|
|||
private SparkplugBProto.Payload.Metric.Builder setMetricValue(SparkplugBProto.Payload.Metric.Builder metricBuilder, |
|||
Metric metric) throws Exception { |
|||
|
|||
// Set the data type
|
|||
metricBuilder.setDatatype(metric.getDataType().toIntValue()); |
|||
|
|||
if (metric.getValue() == null) { |
|||
metricBuilder.setIsNull(true); |
|||
} else { |
|||
switch (metric.getDataType()) { |
|||
case Boolean: |
|||
metricBuilder.setBooleanValue(toBoolean(metric.getValue())); |
|||
break; |
|||
case DateTime: |
|||
metricBuilder.setLongValue(((Date) metric.getValue()).getTime()); |
|||
break; |
|||
case File: |
|||
metricBuilder.setBytesValue(ByteString.copyFrom(((File) metric.getValue()).getBytes())); |
|||
SparkplugBProto.Payload.MetaData.Builder metaDataBuilder = |
|||
SparkplugBProto.Payload.MetaData.newBuilder(); |
|||
metaDataBuilder.setFileName(((File) metric.getValue()).getFileName()); |
|||
metricBuilder.setMetadata(metaDataBuilder); |
|||
break; |
|||
case Float: |
|||
metricBuilder.setFloatValue((Float) metric.getValue()); |
|||
break; |
|||
case Double: |
|||
metricBuilder.setDoubleValue((Double) metric.getValue()); |
|||
break; |
|||
case Int8: |
|||
metricBuilder.setIntValue(((Byte)metric.getValue()).intValue()); |
|||
break; |
|||
case Int16: |
|||
case UInt8: |
|||
metricBuilder.setIntValue(((Short)metric.getValue()).intValue()); |
|||
break; |
|||
case Int32: |
|||
case UInt16: |
|||
metricBuilder.setIntValue((int) metric.getValue()); |
|||
break; |
|||
case UInt32: |
|||
case Int64: |
|||
metricBuilder.setLongValue((Long) metric.getValue()); |
|||
break; |
|||
case UInt64: |
|||
metricBuilder.setLongValue(((BigInteger) metric.getValue()).longValue()); |
|||
break; |
|||
case String: |
|||
case Text: |
|||
case UUID: |
|||
metricBuilder.setStringValue((String) metric.getValue()); |
|||
break; |
|||
case Bytes: |
|||
metricBuilder.setBytesValue(ByteString.copyFrom((byte[]) metric.getValue())); |
|||
break; |
|||
case DataSet: |
|||
DataSet dataSet = (DataSet) metric.getValue(); |
|||
SparkplugBProto.Payload.DataSet.Builder dataSetBuilder = |
|||
SparkplugBProto.Payload.DataSet.newBuilder(); |
|||
|
|||
dataSetBuilder.setNumOfColumns(dataSet.getNumOfColumns()); |
|||
|
|||
// Column names
|
|||
List<String> columnNames = dataSet.getColumnNames(); |
|||
if (columnNames != null && !columnNames.isEmpty()) { |
|||
for (String name : columnNames) { |
|||
// Add the column name
|
|||
dataSetBuilder.addColumns(name); |
|||
} |
|||
} |
|||
|
|||
// Column types
|
|||
List<DataSetDataType> columnTypes = dataSet.getTypes(); |
|||
if (columnTypes != null && !columnTypes.isEmpty()) { |
|||
for (DataSetDataType type : columnTypes) { |
|||
// Add the column type
|
|||
dataSetBuilder.addTypes(type.toIntValue()); |
|||
} |
|||
} |
|||
|
|||
// Dataset rows
|
|||
List<Row> rows = dataSet.getRows(); |
|||
if (rows != null && !rows.isEmpty()) { |
|||
for (Row row : rows) { |
|||
SparkplugBProto.Payload.DataSet.Row.Builder protoRowBuilder = |
|||
SparkplugBProto.Payload.DataSet.Row.newBuilder(); |
|||
List<SparkplugValue<?>> values = row.getValues(); |
|||
if (values != null && !values.isEmpty()) { |
|||
for (SparkplugValue<?> value : values) { |
|||
// Add the converted element
|
|||
protoRowBuilder.addElements(convertDataSetValue(value)); |
|||
} |
|||
|
|||
dataSetBuilder.addRows(protoRowBuilder); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Finally add the dataset
|
|||
metricBuilder.setDatasetValue(dataSetBuilder); |
|||
break; |
|||
case Template: |
|||
Template template = (Template) metric.getValue(); |
|||
SparkplugBProto.Payload.Template.Builder templateBuilder = |
|||
SparkplugBProto.Payload.Template.newBuilder(); |
|||
|
|||
// Set isDefinition
|
|||
templateBuilder.setIsDefinition(template.isDefinition()); |
|||
|
|||
// Set Version
|
|||
if (template.getVersion() != null) { |
|||
templateBuilder.setVersion(template.getVersion()); |
|||
} |
|||
|
|||
// Set Template Reference
|
|||
if (template.getTemplateRef() != null) { |
|||
templateBuilder.setTemplateRef(template.getTemplateRef()); |
|||
} |
|||
|
|||
// Set the template metrics
|
|||
if (template.getMetrics() != null) { |
|||
for (Metric templateMetric : template.getMetrics()) { |
|||
templateBuilder.addMetrics(convertMetric(templateMetric)); |
|||
} |
|||
} |
|||
|
|||
// Set the template parameters
|
|||
if (template.getParameters() != null) { |
|||
for (Parameter parameter : template.getParameters()) { |
|||
templateBuilder.addParameters(convertParameter(parameter)); |
|||
} |
|||
} |
|||
|
|||
// Add the template to the metric
|
|||
metricBuilder.setTemplateValue(templateBuilder); |
|||
break; |
|||
case Unknown: |
|||
default: |
|||
logger.error("Unknown DataType: " + metric.getDataType()); |
|||
throw new Exception("Failed to encode"); |
|||
|
|||
} |
|||
} |
|||
return metricBuilder; |
|||
} |
|||
|
|||
private SparkplugBProto.Payload.Metric.Builder setMetaData(SparkplugBProto.Payload.Metric.Builder metricBuilder, |
|||
Metric metric) throws Exception { |
|||
|
|||
// If the builder has been built already - use it
|
|||
SparkplugBProto.Payload.MetaData.Builder metaDataBuilder = metricBuilder.getMetadataBuilder() != null |
|||
? metricBuilder.getMetadataBuilder() |
|||
: SparkplugBProto.Payload.MetaData.newBuilder(); |
|||
|
|||
MetaData metaData = metric.getMetaData(); |
|||
if (metaData.getContentType() != null) { |
|||
metaDataBuilder.setContentType(metaData.getContentType()); |
|||
} |
|||
if (metaData.getSize() != null) { |
|||
metaDataBuilder.setSize(metaData.getSize()); |
|||
} |
|||
if (metaData.getSeq() != null) { |
|||
metaDataBuilder.setSeq(metaData.getSeq()); |
|||
} |
|||
if (metaData.getFileName() != null) { |
|||
metaDataBuilder.setFileName(metaData.getFileName()); |
|||
} |
|||
if (metaData.getFileType() != null) { |
|||
metaDataBuilder.setFileType(metaData.getFileType()); |
|||
} |
|||
if (metaData.getMd5() != null) { |
|||
metaDataBuilder.setMd5(metaData.getMd5()); |
|||
} |
|||
if (metaData.getDescription() != null) { |
|||
metaDataBuilder.setDescription(metaData.getDescription()); |
|||
} |
|||
metricBuilder.setMetadata(metaDataBuilder); |
|||
|
|||
return metricBuilder; |
|||
} |
|||
|
|||
private SparkplugBProto.Payload.DataSet.DataSetValue.Builder convertDataSetValue(SparkplugValue<?> value) throws Exception { |
|||
SparkplugBProto.Payload.DataSet.DataSetValue.Builder protoValueBuilder = |
|||
SparkplugBProto.Payload.DataSet.DataSetValue.newBuilder(); |
|||
|
|||
// Set the value
|
|||
DataSetDataType type = value.getType(); |
|||
switch (type) { |
|||
case Int8: |
|||
protoValueBuilder.setIntValue((Byte) value.getValue()); |
|||
break; |
|||
case Int16: |
|||
case UInt8: |
|||
protoValueBuilder.setIntValue((Short) value.getValue()); |
|||
break; |
|||
case Int32: |
|||
case UInt16: |
|||
protoValueBuilder.setIntValue((Integer) value.getValue()); |
|||
break; |
|||
case Int64: |
|||
case UInt32: |
|||
protoValueBuilder.setLongValue((Long) value.getValue()); |
|||
break; |
|||
case UInt64: |
|||
protoValueBuilder.setLongValue(((BigInteger) value.getValue()).longValue()); |
|||
break; |
|||
case Float: |
|||
protoValueBuilder.setFloatValue((Float) value.getValue()); |
|||
break; |
|||
case Double: |
|||
protoValueBuilder.setDoubleValue((Double) value.getValue()); |
|||
break; |
|||
case String: |
|||
case Text: |
|||
if (value.getValue() != null) { |
|||
protoValueBuilder.setStringValue((String) value.getValue()); |
|||
} else { |
|||
logger.warn("String value for dataset is null"); |
|||
protoValueBuilder.setStringValue("null"); |
|||
} |
|||
break; |
|||
case Boolean: |
|||
protoValueBuilder.setBooleanValue(toBoolean(value.getValue())); |
|||
break; |
|||
case DateTime: |
|||
try { |
|||
protoValueBuilder.setLongValue(((Date) value.getValue()).getTime()); |
|||
} catch (NullPointerException npe) { |
|||
// FIXME - remove after is_null is supported for dataset values
|
|||
logger.debug("Date in dataset was null - leaving it -9223372036854775808L"); |
|||
protoValueBuilder.setLongValue(-9223372036854775808L); |
|||
} |
|||
break; |
|||
default: |
|||
logger.error("Unknown DataType: " + value.getType()); |
|||
throw new Exception("Failed to convert value " + value.getType()); |
|||
} |
|||
|
|||
return protoValueBuilder; |
|||
} |
|||
|
|||
private Boolean toBoolean(Object value) { |
|||
if (value == null) { |
|||
return null; |
|||
} |
|||
if (value instanceof Integer) { |
|||
return ((Integer)value).intValue() == 0 ? Boolean.FALSE : Boolean.TRUE; |
|||
} else if (value instanceof Long) { |
|||
return ((Long)value).longValue() == 0 ? Boolean.FALSE : Boolean.TRUE; |
|||
} else if (value instanceof Float) { |
|||
return ((Float)value).floatValue() == 0 ? Boolean.FALSE : Boolean.TRUE; |
|||
} else if (value instanceof Double) { |
|||
return ((Double)value).doubleValue() == 0 ? Boolean.FALSE : Boolean.TRUE; |
|||
} else if (value instanceof Short) { |
|||
return ((Short)value).shortValue() == 0 ? Boolean.FALSE : Boolean.TRUE; |
|||
} else if (value instanceof Byte) { |
|||
return ((Byte)value).byteValue() == 0 ? Boolean.FALSE : Boolean.TRUE; |
|||
} else if (value instanceof String) { |
|||
return Boolean.parseBoolean(value.toString()); |
|||
} |
|||
return (Boolean)value; |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,107 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug; |
|||
|
|||
/** |
|||
* An enumeration of Sparkplug MQTT message types. The type provides an indication as to what the MQTT Payload of |
|||
* message will contain. |
|||
*/ |
|||
public enum SparkplugMessageType { |
|||
|
|||
/** |
|||
* Birth certificate for MQTT Edge of Network (EoN) Nodes. |
|||
*/ |
|||
NBIRTH, |
|||
|
|||
/** |
|||
* Death certificate for MQTT Edge of Network (EoN) Nodes. |
|||
*/ |
|||
NDEATH, |
|||
|
|||
/** |
|||
* Birth certificate for MQTT Devices. |
|||
*/ |
|||
DBIRTH, |
|||
|
|||
/** |
|||
* Death certificate for MQTT Devices. |
|||
*/ |
|||
DDEATH, |
|||
|
|||
/** |
|||
* Edge of Network (EoN) Node data message. |
|||
*/ |
|||
NDATA, |
|||
|
|||
/** |
|||
* Device data message. |
|||
*/ |
|||
DDATA, |
|||
|
|||
/** |
|||
* Edge of Network (EoN) Node command message. |
|||
*/ |
|||
NCMD, |
|||
|
|||
/** |
|||
* Device command message. |
|||
*/ |
|||
DCMD, |
|||
|
|||
/** |
|||
* Critical application state message. |
|||
*/ |
|||
STATE, |
|||
|
|||
/** |
|||
* Device record message. |
|||
*/ |
|||
DRECORD, |
|||
|
|||
/** |
|||
* Edge of Network (EoN) Node record message. |
|||
*/ |
|||
NRECORD; |
|||
|
|||
public static SparkplugMessageType parseMessageType(String type) throws Exception { |
|||
for (SparkplugMessageType messageType : SparkplugMessageType.values()) { |
|||
if (messageType.name().equals(type)) { |
|||
return messageType; |
|||
} |
|||
} |
|||
throw new Exception("Invalid message type: " + type); |
|||
} |
|||
|
|||
public boolean isDeath() { |
|||
return this.equals(DDEATH) || this.equals(NDEATH); |
|||
} |
|||
|
|||
public boolean isCommand() { |
|||
return this.equals(DCMD) || this.equals(NCMD); |
|||
} |
|||
|
|||
public boolean isData() { |
|||
return this.equals(DDATA) || this.equals(NDATA); |
|||
} |
|||
|
|||
public boolean isBirth() { |
|||
return this.equals(DBIRTH) || this.equals(NBIRTH); |
|||
} |
|||
|
|||
public boolean isRecord() { |
|||
return this.equals(DRECORD) || this.equals(NRECORD); |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug; |
|||
|
|||
/** |
|||
* Created by nickAS21 on 13.12.22 |
|||
*/ |
|||
|
|||
/** |
|||
* An interface for decoding payloads. |
|||
* |
|||
* @param <P> the type of payload. |
|||
*/ |
|||
public interface SparkplugPayloadDecoder <P> { |
|||
|
|||
/** |
|||
* Builds a payload from a supplied byte array. |
|||
* |
|||
* @param bytes the bytes representing the payload |
|||
* @return a payload object built from the byte array |
|||
* @throws Exception |
|||
*/ |
|||
public P buildFromByteArray(byte[] bytes) throws Exception; |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug; |
|||
|
|||
/** |
|||
* Created by nickAS21 on 13.12.22 |
|||
*/ |
|||
|
|||
import java.io.IOException; |
|||
|
|||
/** |
|||
* An interface for decoding payloads. |
|||
* |
|||
* @param <P> the type of payload. |
|||
*/ |
|||
public interface SparkplugPayloadEncoder<P> { |
|||
|
|||
/** |
|||
* Converts a payload object into a byte array. |
|||
* |
|||
* @param payload a payload object |
|||
* @return the byte array representing the payload |
|||
* @throws IOException |
|||
*/ |
|||
public byte[] getBytes(P payload) throws IOException; |
|||
} |
|||
@ -0,0 +1,113 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug; |
|||
|
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.SparkplugTopic; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* Provides utility methods for handling Sparkplug MQTT message topics. |
|||
*/ |
|||
public class SparkplugTopicUtil { |
|||
|
|||
private static final Map<String, String[]> SPLIT_TOPIC_CACHE = new HashMap<String, String[]>(); |
|||
|
|||
public static String[] getSplitTopic(String topic) { |
|||
String[] splitTopic = SPLIT_TOPIC_CACHE.get(topic); |
|||
if (splitTopic == null) { |
|||
splitTopic = topic.split("/"); |
|||
SPLIT_TOPIC_CACHE.put(topic, splitTopic); |
|||
} |
|||
|
|||
return splitTopic; |
|||
} |
|||
|
|||
/** |
|||
* Serializes a {@link SparkplugTopic} instance in to a JSON string. |
|||
* |
|||
* @param topic a {@link SparkplugTopic} instance |
|||
* @return a JSON string |
|||
* @throws JsonProcessingException |
|||
*/ |
|||
public static String sparkplugTopicToString(SparkplugTopic topic) throws JsonProcessingException { |
|||
ObjectMapper mapper = new ObjectMapper(); |
|||
return mapper.writeValueAsString(topic); |
|||
} |
|||
|
|||
/** |
|||
* Parses a Sparkplug MQTT message topic string and returns a {@link SparkplugTopic} instance. |
|||
* |
|||
* @param topic a topic string |
|||
* @return a {@link SparkplugTopic} instance |
|||
* @throws Exception if an error occurs while parsing |
|||
*/ |
|||
public static SparkplugTopic parseTopic(String topic) throws Exception { |
|||
topic = topic.indexOf("#") > 0 ? topic.substring(0, topic.indexOf("#")) : topic; |
|||
return parseTopic(SparkplugTopicUtil.getSplitTopic(topic)); |
|||
} |
|||
|
|||
/** |
|||
* Parses a Sparkplug MQTT message topic string and returns a {@link SparkplugTopic} instance. |
|||
* |
|||
* @param splitTopic a topic split into tokens |
|||
* @return a {@link SparkplugTopic} instance |
|||
* @throws Exception if an error occurs while parsing |
|||
*/ |
|||
@SuppressWarnings("incomplete-switch") |
|||
public static SparkplugTopic parseTopic(String[] splitTopic) throws Exception { |
|||
SparkplugMessageType type; |
|||
String namespace, edgeNodeId, groupId; |
|||
int length = splitTopic.length; |
|||
|
|||
if (length < 4 || length > 5) { |
|||
throw new Exception("Invalid number of topic elements: " + length); |
|||
} |
|||
|
|||
namespace = splitTopic[0]; |
|||
groupId = splitTopic[1]; |
|||
type = SparkplugMessageType.parseMessageType(splitTopic[2]); |
|||
edgeNodeId = splitTopic[3]; |
|||
|
|||
if (length == 4) { |
|||
// A node topic
|
|||
switch (type) { |
|||
case STATE: |
|||
case NBIRTH: |
|||
case NCMD: |
|||
case NDATA: |
|||
case NDEATH: |
|||
case NRECORD: |
|||
return new SparkplugTopic(namespace, groupId, edgeNodeId, type); |
|||
} |
|||
} else { |
|||
// A device topic
|
|||
switch (type) { |
|||
case STATE: |
|||
case DBIRTH: |
|||
case DCMD: |
|||
case DDATA: |
|||
case DDEATH: |
|||
case DRECORD: |
|||
return new SparkplugTopic(namespace, groupId, edgeNodeId, splitTopic[4], type); |
|||
} |
|||
} |
|||
throw new Exception("Invalid number of topic elements " + length + " for topic type " + type); |
|||
} |
|||
} |
|||
@ -0,0 +1,129 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.json; |
|||
|
|||
import com.fasterxml.jackson.core.JsonParser; |
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import com.fasterxml.jackson.databind.DeserializationContext; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer; |
|||
import org.apache.log4j.LogManager; |
|||
import org.apache.log4j.Logger; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.DataSet; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.DataSetDataType; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Row; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.SparkplugValue; |
|||
|
|||
import java.io.IOException; |
|||
import java.math.BigInteger; |
|||
import java.util.ArrayList; |
|||
import java.util.Date; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* A JSON deserializer for {@link DataSet} instances. |
|||
*/ |
|||
public class DataSetDeserializer extends StdDeserializer<DataSet> { |
|||
|
|||
private static Logger logger = LogManager.getLogger(DataSetDeserializer.class.getName()); |
|||
|
|||
private static final String FIELD_SIZE = "numberOfColumns"; |
|||
private static final String FIELD_TYPES = "types"; |
|||
private static final String FIELD_NAMES = "columnNames"; |
|||
private static final String FIELD_ROWS = "rows"; |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param clazz |
|||
*/ |
|||
protected DataSetDeserializer(Class<DataSet> clazz) { |
|||
super(clazz); |
|||
} |
|||
|
|||
@Override |
|||
public DataSet deserialize(JsonParser parser, DeserializationContext context) |
|||
throws IOException, JsonProcessingException { |
|||
JsonNode node = parser.getCodec().readTree(parser); |
|||
long size = (Long) node.get(FIELD_SIZE).numberValue(); |
|||
DataSet.DataSetBuilder builder = new DataSet.DataSetBuilder(size); |
|||
JsonNode namesNode = node.get(FIELD_NAMES); |
|||
if (namesNode.isArray()) { |
|||
for (JsonNode nameNode : namesNode) { |
|||
builder.addColumnName(nameNode.textValue()); |
|||
} |
|||
} |
|||
JsonNode typesNode = node.get(FIELD_TYPES); |
|||
List<DataSetDataType> typesList = new ArrayList<DataSetDataType>(); |
|||
if (typesNode.isArray()) { |
|||
for (JsonNode typeNode : typesNode) { |
|||
typesList.add(DataSetDataType.valueOf(typeNode.textValue())); |
|||
} |
|||
builder.addTypes(typesList); |
|||
} |
|||
JsonNode rowsNode = node.get(FIELD_ROWS); |
|||
if (rowsNode.isArray()) { |
|||
for (JsonNode rowNode : rowsNode) { |
|||
List<SparkplugValue<?>> values = new ArrayList<SparkplugValue<?>>(); |
|||
for (int i = 0; i < size; i++) { |
|||
JsonNode value = rowNode.get(i); |
|||
DataSetDataType type = typesList.get(i); |
|||
values.add(getValueFromNode(value, type)); |
|||
} |
|||
builder.addRow(new Row(values)); |
|||
} |
|||
} |
|||
try { |
|||
return builder.createDataSet(); |
|||
} catch (Exception e) { |
|||
logger.error("Error deserializing DataSet ", e); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
/* |
|||
* Creates and returns a Value instance |
|||
*/ |
|||
private SparkplugValue<?> getValueFromNode(JsonNode nodeValue, DataSetDataType type) { |
|||
switch (type) { |
|||
case Boolean: |
|||
return new SparkplugValue<Boolean>(type, (boolean)nodeValue.asBoolean()); |
|||
case DateTime: |
|||
return new SparkplugValue<Date>(type, new Date(nodeValue.asLong())); |
|||
case Double: |
|||
return new SparkplugValue<Double>(type, nodeValue.asDouble()); |
|||
case Float: |
|||
return new SparkplugValue<Float>(type, (float)nodeValue.asDouble()); |
|||
case Int16: |
|||
case UInt8: |
|||
return new SparkplugValue<Byte>(type, (byte)nodeValue.asInt()); |
|||
case UInt16: |
|||
case Int32: |
|||
return new SparkplugValue<Integer>(type, nodeValue.asInt()); |
|||
case UInt32: |
|||
case Int64: |
|||
return new SparkplugValue<Long>(type, (long)nodeValue.asLong()); |
|||
case Text: |
|||
case String: |
|||
return new SparkplugValue<String>(type, nodeValue.asText()); |
|||
case UInt64: |
|||
return new SparkplugValue<BigInteger>(type, BigInteger.valueOf(nodeValue.asLong())); |
|||
case Unknown: |
|||
default: |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.json; |
|||
|
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Metric; |
|||
import com.fasterxml.jackson.databind.BeanDescription; |
|||
import com.fasterxml.jackson.databind.DeserializationConfig; |
|||
import com.fasterxml.jackson.databind.JsonDeserializer; |
|||
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; |
|||
|
|||
/** |
|||
* |
|||
*/ |
|||
public class DeserializerModifier extends BeanDeserializerModifier { |
|||
|
|||
@Override |
|||
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) { |
|||
if (Metric.class.equals(beanDesc.getBeanClass())) { |
|||
return new MetricDeserializer(deserializer); |
|||
} |
|||
|
|||
return super.modifyDeserializer(config, beanDesc, deserializer); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.json; |
|||
|
|||
import com.fasterxml.jackson.core.Version; |
|||
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; |
|||
import com.fasterxml.jackson.databind.module.SimpleModule; |
|||
|
|||
/** |
|||
* Used to register the {@link DeserializerModifier} instance. |
|||
*/ |
|||
public class DeserializerModule extends SimpleModule { |
|||
|
|||
private BeanDeserializerModifier deserializerModifier; |
|||
|
|||
public DeserializerModule(BeanDeserializerModifier deserializerModifier) { |
|||
super("DeserializerModule", Version.unknownVersion()); |
|||
this.deserializerModifier = deserializerModifier; |
|||
} |
|||
|
|||
@Override |
|||
public void setupModule(SetupContext context) { |
|||
super.setupModule(context); |
|||
context.addBeanDeserializerModifier(deserializerModifier); |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.json; |
|||
|
|||
import com.fasterxml.jackson.core.JsonGenerator; |
|||
import com.fasterxml.jackson.databind.SerializerProvider; |
|||
import com.fasterxml.jackson.databind.ser.std.StdSerializer; |
|||
import org.eclipse.californium.elements.util.Base64; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.File; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
/** |
|||
* Serializes a {@link File} instance. |
|||
*/ |
|||
public class FileSerializer extends StdSerializer<File> { |
|||
|
|||
/** |
|||
* Constructor. |
|||
*/ |
|||
protected FileSerializer() { |
|||
super(File.class); |
|||
} |
|||
|
|||
/** |
|||
* Constructor. |
|||
* |
|||
* @param clazz class. |
|||
*/ |
|||
protected FileSerializer(Class<File> clazz) { |
|||
super(clazz); |
|||
} |
|||
|
|||
@Override |
|||
public void serialize(File value, JsonGenerator generator, SerializerProvider provider) throws IOException { |
|||
generator.writeString(Base64.encodeBytes(value.getBytes())); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.json; |
|||
|
|||
/** |
|||
* Validates JSON. |
|||
*/ |
|||
public class JsonValidator { |
|||
|
|||
protected static final String JSON_SCHEMA_FILENAME = "payload.json"; |
|||
|
|||
private static JsonValidator instance = null; |
|||
|
|||
/** |
|||
* Constructor. |
|||
*/ |
|||
protected JsonValidator() { |
|||
} |
|||
|
|||
/** |
|||
* Returns the {@link JsonValidator} instance. |
|||
* |
|||
* @return the {@link JsonValidator} instance. |
|||
*/ |
|||
public static JsonValidator getInstance() { |
|||
if (instance == null) { |
|||
instance = new JsonValidator(); |
|||
} |
|||
return instance; |
|||
} |
|||
|
|||
/** |
|||
* Returns loads and returns the {@link JsonSchema} instance associated with this validator. |
|||
* |
|||
* @return the {@link JsonSchema} instance associated with this validator. |
|||
* @throws IOException |
|||
* @throws ProcessingException |
|||
*/ |
|||
/* protected JsonSchema getSchema() throws IOException, ProcessingException { |
|||
//Get file from resources folder
|
|||
ClassLoader classLoader = getClass().getClassLoader(); |
|||
File schemaFile = new File(classLoader.getResource(JSON_SCHEMA_FILENAME).getFile()); |
|||
return JsonSchemaFactory.byDefault().getJsonSchema(JsonLoader.fromFile(schemaFile)); |
|||
}*/ |
|||
|
|||
/** |
|||
* Returns true if the supplied JSON text is valid, false otherwise. |
|||
* |
|||
* @param jsonText a {@link String} representing JSON text. |
|||
* @return true if the supplied JSON text is valid, false otherwise. |
|||
* @throws ProcessingException |
|||
* @throws IOException |
|||
*/ |
|||
/* public boolean isJsonValid(String jsonText) throws ProcessingException, IOException { |
|||
return getSchema().validate(JsonLoader.fromString(jsonText)).isSuccess(); |
|||
}*/ |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.json; |
|||
|
|||
import com.fasterxml.jackson.core.JsonParser; |
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import com.fasterxml.jackson.databind.DeserializationContext; |
|||
import com.fasterxml.jackson.databind.JsonDeserializer; |
|||
import com.fasterxml.jackson.databind.JsonMappingException; |
|||
import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; |
|||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.File; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.MetaData; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.Metric; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.MetricDataType; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.Base64; |
|||
|
|||
/** |
|||
* A custom JSON deserializer for {@link Metric} instances. |
|||
*/ |
|||
public class MetricDeserializer extends StdDeserializer<Metric> implements ResolvableDeserializer { |
|||
|
|||
private final JsonDeserializer<?> defaultDeserializer; |
|||
|
|||
/** |
|||
* Constructor. |
|||
*/ |
|||
protected MetricDeserializer(JsonDeserializer<?> defaultDeserializer) { |
|||
super(Metric.class); |
|||
this.defaultDeserializer = defaultDeserializer; |
|||
} |
|||
|
|||
@Override |
|||
public Metric deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException { |
|||
|
|||
Metric metric = (Metric) defaultDeserializer.deserialize(parser, ctxt); |
|||
System.out.println(metric); |
|||
|
|||
// Check if the data type is a File
|
|||
if (metric.getDataType().equals(MetricDataType.File)) { |
|||
// Perform the custom logic for File types by building up the File object.
|
|||
MetaData metaData = metric.getMetaData(); |
|||
String fileName = metaData == null ? null : metaData.getFileName(); |
|||
File file = new File(fileName, Base64.getDecoder().decode((String)metric.getValue())); |
|||
metric.setValue(file); |
|||
} |
|||
return metric; |
|||
} |
|||
|
|||
@Override |
|||
public void resolve(DeserializationContext ctxt) throws JsonMappingException { |
|||
((ResolvableDeserializer) defaultDeserializer).resolve(ctxt); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,234 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.message; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonGetter; |
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.json.DataSetDeserializer; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Collection; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* A data set that represents a table of data. |
|||
*/ |
|||
@Slf4j |
|||
@JsonDeserialize(using = DataSetDeserializer.class) |
|||
public class DataSet { |
|||
|
|||
/** |
|||
* The number of columns |
|||
*/ |
|||
@JsonProperty("numberOfColumns") |
|||
private long numOfColumns; |
|||
|
|||
/** |
|||
* A list containing the names of each column |
|||
*/ |
|||
@JsonProperty("columnNames") |
|||
private List<String> columnNames; |
|||
|
|||
/** |
|||
* A list containing the data types of each column |
|||
*/ |
|||
@JsonProperty("types") |
|||
private List<DataSetDataType> types; |
|||
|
|||
/** |
|||
* A list containing the rows in the data set |
|||
*/ |
|||
private List<Row> rows; |
|||
|
|||
public DataSet(long numOfColumns, List<String> columnNames, List<DataSetDataType> types, List<Row> rows) { |
|||
this.numOfColumns = numOfColumns; |
|||
this.columnNames = columnNames; |
|||
this.types = types; |
|||
this.rows = rows; |
|||
} |
|||
|
|||
public long getNumOfColumns() { |
|||
return numOfColumns; |
|||
} |
|||
|
|||
public void setNumOfColumns(long numOfColumns) { |
|||
this.numOfColumns = numOfColumns; |
|||
} |
|||
|
|||
public List<String> getColumnNames() { |
|||
return columnNames; |
|||
} |
|||
|
|||
public void setColumnNames(List<String> columnNames) { |
|||
this.columnNames = columnNames; |
|||
} |
|||
|
|||
public void addColumnName(String columnName) { |
|||
this.columnNames.add(columnName); |
|||
} |
|||
|
|||
public List<Row> getRows() { |
|||
return rows; |
|||
} |
|||
|
|||
@JsonGetter("rows") |
|||
public List<List<Object>> getRowsAsLists() { |
|||
List<List<Object>> list = new ArrayList<List<Object>>(getRows().size()); |
|||
for (Row row : getRows()) { |
|||
list.add(Row.toValues(row)); |
|||
} |
|||
return list; |
|||
} |
|||
|
|||
public void addRow(Row row) { |
|||
this.rows.add(row); |
|||
} |
|||
|
|||
public void addRow(int index, Row row) { |
|||
this.rows.add(index, row); |
|||
} |
|||
|
|||
public Row removeRow(int index) { |
|||
return rows.remove(index); |
|||
} |
|||
|
|||
public boolean removeRow(Row row) { |
|||
return rows.remove(row); |
|||
} |
|||
|
|||
public void setRows(List<Row> rows) { |
|||
this.rows = rows; |
|||
} |
|||
|
|||
public List<DataSetDataType> getTypes() { |
|||
return types; |
|||
} |
|||
|
|||
public void setTypes(List<DataSetDataType> types) { |
|||
this.types = types; |
|||
} |
|||
|
|||
public void addType(DataSetDataType type) { |
|||
this.types.add(type); |
|||
} |
|||
|
|||
public void addType(int index, DataSetDataType type) { |
|||
this.types.add(index, type); |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "DataSet [numOfColumns=" + numOfColumns + ", columnNames=" + columnNames + ", types=" + types + ", rows=" |
|||
+ rows + "]"; |
|||
} |
|||
|
|||
/** |
|||
* A builder for creating a {@link DataSet} instance. |
|||
*/ |
|||
public static class DataSetBuilder { |
|||
|
|||
private long numOfColumns; |
|||
private List<String> columnNames; |
|||
private List<DataSetDataType> types; |
|||
private List<Row> rows; |
|||
|
|||
public DataSetBuilder(long numOfColumns) { |
|||
this.numOfColumns = numOfColumns; |
|||
this.columnNames = new ArrayList<String>(); |
|||
this.types = new ArrayList<DataSetDataType>(); |
|||
this.rows = new ArrayList<Row>(); |
|||
} |
|||
|
|||
public DataSetBuilder(DataSet dataSet) { |
|||
this.numOfColumns = dataSet.getNumOfColumns(); |
|||
this.columnNames = new ArrayList<String>(dataSet.getColumnNames()); |
|||
this.types = new ArrayList<DataSetDataType>(dataSet.getTypes()); |
|||
this.rows = new ArrayList<Row>(dataSet.getRows().size()); |
|||
for (Row row : dataSet.getRows()) { |
|||
rows.add(new Row.RowBuilder(row).createRow()); |
|||
} |
|||
} |
|||
|
|||
public DataSetBuilder addColumnNames(Collection<String> columnNames) { |
|||
this.columnNames.addAll(columnNames); |
|||
return this; |
|||
} |
|||
|
|||
public DataSetBuilder addColumnName(String columnName) { |
|||
this.columnNames.add(columnName); |
|||
return this; |
|||
} |
|||
|
|||
public DataSetBuilder addType(DataSetDataType type) { |
|||
this.types.add(type); |
|||
return this; |
|||
} |
|||
|
|||
public DataSetBuilder addTypes(Collection<DataSetDataType> types) { |
|||
this.types.addAll(types); |
|||
return this; |
|||
} |
|||
|
|||
public DataSetBuilder addRow(Row row) { |
|||
this.rows.add(row); |
|||
return this; |
|||
} |
|||
|
|||
public DataSetBuilder addRows(Collection<Row> rows) { |
|||
this.rows.addAll(rows); |
|||
return this; |
|||
} |
|||
|
|||
public DataSet createDataSet() throws Exception { |
|||
log.trace("Number of columns: " + numOfColumns); |
|||
for (String columnName : columnNames) { |
|||
log.trace("\tcolumnName: " + columnName); |
|||
} |
|||
for (DataSetDataType type : types) { |
|||
log.trace("\ttypes: " + type); |
|||
} |
|||
for (Row row : rows) { |
|||
log.trace("\t\trow: " + row); |
|||
} |
|||
|
|||
validate(); |
|||
return new DataSet(numOfColumns, columnNames, types, rows); |
|||
} |
|||
|
|||
public void validate() throws Exception { |
|||
if (columnNames.size() != numOfColumns) { |
|||
throw new Exception("Invalid number of columns in data set column names: " + |
|||
columnNames.size() + " vs expected " + numOfColumns); |
|||
} |
|||
if (types.size() != numOfColumns) { |
|||
throw new Exception("Invalid number of columns in data set types: " + |
|||
types.size() + " vs expected: " + numOfColumns); |
|||
} |
|||
for (int i = 0; i < types.size(); i++) { |
|||
for (Row row : rows) { |
|||
List<SparkplugValue<?>> values = row.getValues(); |
|||
if (values.size() != numOfColumns) { |
|||
throw new Exception("Invalid number of columns in data set row: " + |
|||
values.size() + " vs expected: " + numOfColumns); |
|||
} |
|||
types.get(i).checkType(row.getValues().get(i).getValue()); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.message; |
|||
|
|||
import java.math.BigInteger; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* A enumeration of data types of values in a {@link DataSet} |
|||
*/ |
|||
public enum DataSetDataType { |
|||
|
|||
// Basic Types
|
|||
Int8(1, Byte.class), |
|||
Int16(2, Short.class), |
|||
Int32(3, Integer.class), |
|||
Int64(4, Long.class), |
|||
UInt8(5, Short.class), |
|||
UInt16(6, Integer.class), |
|||
UInt32(7, Long.class), |
|||
UInt64(8, BigInteger.class), |
|||
Float(9, Float.class), |
|||
Double(10, Double.class), |
|||
Boolean(11, Boolean.class), |
|||
String(12, String.class), |
|||
DateTime(13, Date.class), |
|||
Text(14, String.class), |
|||
|
|||
// Unknown
|
|||
Unknown(0, Object.class); |
|||
|
|||
private Class<?> clazz = null; |
|||
private int intValue = 0; |
|||
|
|||
private DataSetDataType(int intValue, Class<?> clazz) { |
|||
this.intValue = intValue; |
|||
this.clazz = clazz; |
|||
} |
|||
|
|||
public void checkType(Object value) throws Exception { |
|||
if (value != null && !value.getClass().equals(clazz)) { |
|||
throw new Exception(value.getClass().getName()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Returns an integer representation of the data type. |
|||
* |
|||
* @return an integer representation of the data type. |
|||
*/ |
|||
public int toIntValue() { |
|||
return this.intValue; |
|||
} |
|||
|
|||
/** |
|||
* Converts the integer representation of the data type into a {@link DataSetDataType} instance. |
|||
* |
|||
* @param i the integer representation of the data type. |
|||
* @return a {@link DataSetDataType} instance. |
|||
*/ |
|||
public static DataSetDataType fromInteger(int i) { |
|||
switch(i) { |
|||
case 1: |
|||
return Int8; |
|||
case 2: |
|||
return Int16; |
|||
case 3: |
|||
return Int32; |
|||
case 4: |
|||
return Int64; |
|||
case 5: |
|||
return UInt8; |
|||
case 6: |
|||
return UInt16; |
|||
case 7: |
|||
return UInt32; |
|||
case 8: |
|||
return UInt64; |
|||
case 9: |
|||
return Float; |
|||
case 10: |
|||
return Double; |
|||
case 11: |
|||
return Boolean; |
|||
case 12: |
|||
return String; |
|||
case 13: |
|||
return DateTime; |
|||
case 14: |
|||
return Text; |
|||
default: |
|||
return Unknown; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Returns the class type for this DataType |
|||
* |
|||
* @return the class type for this DataType |
|||
*/ |
|||
public Class<?> getClazz() { |
|||
return clazz; |
|||
} |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.message; |
|||
|
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.json.FileSerializer; |
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import com.fasterxml.jackson.databind.annotation.JsonSerialize; |
|||
|
|||
import java.util.Arrays; |
|||
|
|||
@JsonIgnoreProperties(value = { "fileName" }) |
|||
@JsonSerialize(using = FileSerializer.class) |
|||
public class File { |
|||
|
|||
private String fileName; |
|||
private byte[] bytes; |
|||
|
|||
public File() { |
|||
super(); |
|||
} |
|||
|
|||
public File(String fileName, byte[] bytes) { |
|||
super(); |
|||
this.fileName = fileName == null |
|||
? null |
|||
: fileName.replace("/", System.getProperty("file.separator")) |
|||
.replace("\\", System.getProperty("file.separator")); |
|||
this.bytes = Arrays.copyOf(bytes, bytes.length); |
|||
} |
|||
|
|||
public String getFileName() { |
|||
return fileName; |
|||
} |
|||
|
|||
public void setFileName(String fileName) { |
|||
this.fileName = fileName; |
|||
} |
|||
|
|||
public byte[] getBytes() { |
|||
return bytes; |
|||
} |
|||
|
|||
public void setBytes(byte[] bytes) { |
|||
this.bytes = bytes; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "File [fileName=" + fileName + ", bytes length=" + bytes.length + "]"; |
|||
} |
|||
} |
|||
@ -0,0 +1,267 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.message; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonInclude; |
|||
import com.fasterxml.jackson.annotation.JsonInclude.Include; |
|||
|
|||
import java.util.Objects; |
|||
|
|||
/** |
|||
* A class to represent the meta data associated with a metric. |
|||
*/ |
|||
@JsonInclude(Include.NON_NULL) |
|||
public class MetaData { |
|||
|
|||
/** |
|||
* Indicates if the metric represents one of multiple parts. |
|||
*/ |
|||
private Boolean isMultiPart; |
|||
|
|||
/** |
|||
* A content type associated with the metric. |
|||
*/ |
|||
private String contentType; |
|||
|
|||
/** |
|||
* A size associated with the metric. |
|||
*/ |
|||
private Long size; |
|||
|
|||
/** |
|||
* A sequence associated with the metric. |
|||
*/ |
|||
private Long seq; |
|||
|
|||
/** |
|||
* A file name associated with the metric. |
|||
*/ |
|||
private String fileName; |
|||
|
|||
/** |
|||
* A file type associated with the metric. |
|||
*/ |
|||
private String fileType; |
|||
|
|||
/** |
|||
* A MD5 sum associated with the metric. |
|||
*/ |
|||
private String md5; |
|||
|
|||
/** |
|||
* A description associated with the metric. |
|||
*/ |
|||
private String description; |
|||
|
|||
/** |
|||
* Default no-arg constructor. |
|||
*/ |
|||
public MetaData() {} |
|||
|
|||
/** |
|||
* Constructor with fields. |
|||
* |
|||
* @param isMultiPart if the metric represents one of multiple parts. |
|||
* @param contentType a content type associated with the metric. |
|||
* @param size a size associated with the metric. |
|||
* @param seq a sequence associated with the metric. |
|||
* @param fileName a file name associated with the metric. |
|||
* @param fileType a file type associated with the metric. |
|||
* @param md5 a MD5 sum associated with the metric. |
|||
* @param description a description associated with the metric |
|||
*/ |
|||
public MetaData(Boolean isMultiPart, String contentType, Long size, Long seq, String fileName, |
|||
String fileType, String md5, String description) { |
|||
this.isMultiPart = isMultiPart; |
|||
this.contentType = contentType; |
|||
this.size = size; |
|||
this.seq = seq; |
|||
this.fileName = fileName; |
|||
this.fileType = fileType; |
|||
this.md5 = md5; |
|||
this.description = description; |
|||
} |
|||
|
|||
public Boolean isMultiPart() { |
|||
return isMultiPart; |
|||
} |
|||
|
|||
public MetaData setMultiPart(Boolean isMultiPart) { |
|||
this.isMultiPart = isMultiPart; |
|||
return this; |
|||
} |
|||
|
|||
public String getContentType() { |
|||
return contentType; |
|||
} |
|||
|
|||
public MetaData setContentType(String contentType) { |
|||
this.contentType = contentType; |
|||
return this; |
|||
} |
|||
|
|||
public Long getSize() { |
|||
return size; |
|||
} |
|||
|
|||
public MetaData setSize(Long size) { |
|||
this.size = size; |
|||
return this; |
|||
} |
|||
|
|||
public Long getSeq() { |
|||
return seq; |
|||
} |
|||
|
|||
public MetaData setSeq(Long seq) { |
|||
this.seq = seq; |
|||
return this; |
|||
} |
|||
|
|||
public String getFileName() { |
|||
return fileName; |
|||
} |
|||
|
|||
public MetaData setFileName(String fileName) { |
|||
this.fileName = fileName; |
|||
return this; |
|||
} |
|||
|
|||
public String getFileType() { |
|||
return fileType; |
|||
} |
|||
|
|||
public MetaData setFileType(String fileType) { |
|||
this.fileType = fileType; |
|||
return this; |
|||
} |
|||
|
|||
public String getMd5() { |
|||
return md5; |
|||
} |
|||
|
|||
public MetaData setMd5(String md5) { |
|||
this.md5 = md5; |
|||
return this; |
|||
} |
|||
|
|||
public String getDescription() { |
|||
return description; |
|||
} |
|||
|
|||
public MetaData setDescription(String description) { |
|||
this.description = description; |
|||
return this; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "MetaData [isMultiPart=" + isMultiPart + ", contentType=" + contentType + ", size=" + size + ", seq=" |
|||
+ seq + ", fileName=" + fileName + ", fileType=" + fileType + ", md5=" + md5 + ", description=" |
|||
+ description + "]"; |
|||
} |
|||
|
|||
@Override |
|||
public boolean equals(Object object) { |
|||
if (this == object) { |
|||
return true; |
|||
} |
|||
if (object == null || this.getClass() != object.getClass()) { |
|||
return false; |
|||
} |
|||
MetaData meta = (MetaData) object; |
|||
return Objects.equals(isMultiPart, meta.isMultiPart()) |
|||
&& Objects.equals(contentType, meta.getContentType()) |
|||
&& Objects.equals(size, meta.getSize()) |
|||
&& Objects.equals(seq, meta.getSeq()) |
|||
&& Objects.equals(fileName, meta.getFileName()) |
|||
&& Objects.equals(fileType, meta.getFileType()) |
|||
&& Objects.equals(md5, meta.getMd5()) |
|||
&& Objects.equals(description, meta.getDescription()); |
|||
} |
|||
|
|||
/** |
|||
* A Builder for a MetaData instance. |
|||
*/ |
|||
public static class MetaDataBuilder { |
|||
|
|||
private Boolean isMultiPart; |
|||
private String contentType; |
|||
private Long size; |
|||
private Long seq; |
|||
private String fileName; |
|||
private String fileType; |
|||
private String md5; |
|||
private String description; |
|||
|
|||
public MetaDataBuilder() {}; |
|||
|
|||
public MetaDataBuilder(MetaData metaData) { |
|||
this.isMultiPart = metaData.isMultiPart(); |
|||
this.contentType = metaData.getContentType(); |
|||
this.size = metaData.getSize(); |
|||
this.seq = metaData.getSeq(); |
|||
this.fileName = metaData.getFileName(); |
|||
this.fileType = metaData.getFileType(); |
|||
this.md5 = metaData.getMd5(); |
|||
this.description = metaData.getDescription(); |
|||
} |
|||
|
|||
public MetaDataBuilder multiPart(Boolean isMultiPart) { |
|||
this.isMultiPart = isMultiPart; |
|||
return this; |
|||
} |
|||
|
|||
public MetaDataBuilder contentType(String contentType) { |
|||
this.contentType = contentType; |
|||
return this; |
|||
} |
|||
|
|||
public MetaDataBuilder size(Long size) { |
|||
this.size = size; |
|||
return this; |
|||
} |
|||
|
|||
public MetaDataBuilder seq(Long seq) { |
|||
this.seq = seq; |
|||
return this; |
|||
} |
|||
|
|||
public MetaDataBuilder fileName(String fileName) { |
|||
this.fileName = fileName; |
|||
return this; |
|||
} |
|||
|
|||
public MetaDataBuilder fileType(String fileType) { |
|||
this.fileType = fileType; |
|||
return this; |
|||
} |
|||
|
|||
public MetaDataBuilder md5(String md5) { |
|||
this.md5 = md5; |
|||
return this; |
|||
} |
|||
|
|||
public MetaDataBuilder description(String description) { |
|||
this.description = description; |
|||
return this; |
|||
} |
|||
|
|||
public MetaData createMetaData() { |
|||
return new MetaData(isMultiPart, contentType, size, seq, fileName, fileType, md5, description); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,320 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.message; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonGetter; |
|||
import com.fasterxml.jackson.annotation.JsonIgnore; |
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import com.fasterxml.jackson.annotation.JsonInclude; |
|||
import com.fasterxml.jackson.annotation.JsonInclude.Include; |
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
import com.fasterxml.jackson.annotation.JsonSetter; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.DataSet.DataSetBuilder; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.message.MetaData.MetaDataBuilder; |
|||
|
|||
import java.util.Date; |
|||
|
|||
; |
|||
|
|||
/** |
|||
* A metric of a Sparkplug Payload. |
|||
*/ |
|||
@JsonIgnoreProperties(value = { "isNull" }) |
|||
@JsonInclude(Include.NON_NULL) |
|||
public class Metric { |
|||
|
|||
@JsonProperty("name") |
|||
private String name; |
|||
|
|||
@JsonProperty("alias") |
|||
private Long alias; |
|||
|
|||
@JsonProperty("timestamp") |
|||
private Date timestamp; |
|||
|
|||
@JsonProperty("dataType") |
|||
private MetricDataType dataType; |
|||
|
|||
@JsonProperty("isHistorical") |
|||
private Boolean isHistorical; |
|||
|
|||
@JsonProperty("isTransient") |
|||
private Boolean isTransient; |
|||
|
|||
@JsonProperty("metaData") |
|||
private MetaData metaData; |
|||
|
|||
@JsonProperty("properties") |
|||
@JsonInclude(Include.NON_EMPTY) |
|||
private PropertySet properties; |
|||
|
|||
@JsonProperty("value") |
|||
private Object value; |
|||
|
|||
private Boolean isNull = null; |
|||
|
|||
public Metric() {}; |
|||
|
|||
/** |
|||
* @param name |
|||
* @param alias |
|||
* @param timestamp |
|||
* @param dataType |
|||
* @param isHistorical |
|||
* @param isTransient |
|||
* @param metaData |
|||
* @param properties |
|||
* @param value |
|||
* @throws Exception |
|||
*/ |
|||
public Metric(String name, Long alias, Date timestamp, MetricDataType dataType, Boolean isHistorical, |
|||
Boolean isTransient, MetaData metaData, PropertySet properties, Object value) |
|||
throws Exception { |
|||
super(); |
|||
this.name = name; |
|||
this.alias = alias; |
|||
this.timestamp = timestamp; |
|||
this.dataType = dataType; |
|||
this.isHistorical = isHistorical; |
|||
this.isTransient = isTransient; |
|||
isNull = (value == null) ? true : false; |
|||
this.metaData = metaData; |
|||
this.properties = properties; |
|||
this.value = value; |
|||
this.dataType.checkType(value); |
|||
} |
|||
|
|||
public String getName() { |
|||
return name; |
|||
} |
|||
|
|||
public void setName(String name) { |
|||
this.name = name; |
|||
} |
|||
|
|||
public boolean hasName() { |
|||
return !(name == null); |
|||
} |
|||
|
|||
public boolean hasAlias() { |
|||
return !(alias == null); |
|||
} |
|||
|
|||
public Long getAlias() { |
|||
return alias; |
|||
} |
|||
|
|||
public void setAlias(long alias) { |
|||
this.alias = alias; |
|||
} |
|||
|
|||
public Date getTimestamp() { |
|||
return timestamp; |
|||
} |
|||
|
|||
public void setTimestamp(Date timestamp) { |
|||
this.timestamp = timestamp; |
|||
} |
|||
|
|||
public MetricDataType getDataType() { |
|||
return dataType; |
|||
} |
|||
|
|||
public void setDataType(MetricDataType dataType) { |
|||
this.dataType = dataType; |
|||
} |
|||
|
|||
@JsonGetter("metaData") |
|||
public MetaData getMetaData() { |
|||
return metaData; |
|||
} |
|||
|
|||
@JsonSetter("metaData") |
|||
public void setMetaData(MetaData metaData) { |
|||
this.metaData = metaData; |
|||
} |
|||
|
|||
public Object getValue() { |
|||
return value; |
|||
} |
|||
|
|||
public void setValue(Object value) { |
|||
this.value = value; |
|||
isNull = (value == null); |
|||
} |
|||
|
|||
public PropertySet getProperties() { |
|||
return this.properties; |
|||
} |
|||
|
|||
public void setProperties(PropertySet properties) { |
|||
this.properties = properties; |
|||
} |
|||
|
|||
@JsonIgnore |
|||
public Boolean isHistorical() { |
|||
return isHistorical == null ? false : isHistorical; |
|||
} |
|||
|
|||
@JsonGetter("isHistorical") |
|||
public Boolean getIsHistorical() { |
|||
return isHistorical; |
|||
} |
|||
|
|||
@JsonSetter("isHistorical") |
|||
public void setHistorical(Boolean isHistorical) { |
|||
this.isHistorical = isHistorical; |
|||
} |
|||
|
|||
@JsonIgnore |
|||
public Boolean isTransient() { |
|||
return isTransient == null ? false : isTransient; |
|||
} |
|||
|
|||
@JsonGetter("isTransient") |
|||
public Boolean getIsTransient() { |
|||
return isTransient; |
|||
} |
|||
|
|||
@JsonSetter("isTransient") |
|||
public void setTransient(Boolean isTransient) { |
|||
this.isTransient = isTransient; |
|||
} |
|||
|
|||
@JsonIgnore |
|||
public Boolean isNull() { |
|||
return isNull == null ? false : isNull; |
|||
} |
|||
|
|||
@JsonIgnore |
|||
public Boolean getIsNull() { |
|||
return isNull; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "Metric [name=" + name + ", alias=" + alias + ", timestamp=" + timestamp + ", dataType=" + dataType |
|||
+ ", isHistorical=" + isHistorical + ", isTransient=" + isTransient + ", isNull=" + isNull |
|||
+ ", metaData=" + metaData + ", propertySet=" + properties + ", value=" + value + "]"; |
|||
} |
|||
|
|||
/** |
|||
* A builder for creating a {@link Metric} instance. |
|||
*/ |
|||
public static class MetricBuilder { |
|||
|
|||
private String name; |
|||
private Long alias; |
|||
private Date timestamp; |
|||
private MetricDataType dataType; |
|||
private Boolean isHistorical; |
|||
private Boolean isTransient; |
|||
private MetaData metaData = null; |
|||
private PropertySet properties = null; |
|||
private Object value; |
|||
|
|||
public MetricBuilder(String name, MetricDataType dataType, Object value) { |
|||
this.name = name; |
|||
this.timestamp = new Date(); |
|||
this.dataType = dataType; |
|||
this.value = value; |
|||
} |
|||
|
|||
public MetricBuilder(Long alias, MetricDataType dataType, Object value) { |
|||
this.alias = alias; |
|||
this.timestamp = new Date(); |
|||
this.dataType = dataType; |
|||
this.value = value; |
|||
} |
|||
|
|||
public MetricBuilder(Metric metric) throws Exception { |
|||
this.name = metric.getName(); |
|||
this.alias = metric.getAlias(); |
|||
this.timestamp = metric.getTimestamp(); |
|||
this.dataType = metric.getDataType(); |
|||
this.isHistorical = metric.isHistorical(); |
|||
this.isTransient = metric.isTransient(); |
|||
this.metaData = metric.getMetaData() != null |
|||
? new MetaDataBuilder(metric.getMetaData()).createMetaData() : null; |
|||
this.properties = metric.getMetaData() != null |
|||
? new PropertySet.PropertySetBuilder(metric.getProperties()).createPropertySet() : null; |
|||
switch (dataType) { |
|||
case DataSet: |
|||
this.value = metric.getValue() != null |
|||
? new DataSetBuilder((DataSet) metric.getValue()).createDataSet() |
|||
: null; |
|||
break; |
|||
case Template: |
|||
this.value = metric.getValue() != null |
|||
? new Template.TemplateBuilder((Template) metric.getValue()).createTemplate() |
|||
: null; |
|||
break; |
|||
default: |
|||
this.value = metric.getValue(); |
|||
} |
|||
} |
|||
|
|||
public MetricBuilder name(String name) { |
|||
this.name = name; |
|||
return this; |
|||
} |
|||
|
|||
public MetricBuilder alias(Long alias) { |
|||
this.alias = alias; |
|||
return this; |
|||
} |
|||
|
|||
public MetricBuilder timestamp(Date timestamp) { |
|||
this.timestamp = timestamp; |
|||
return this; |
|||
} |
|||
|
|||
public MetricBuilder dataType(MetricDataType dataType) { |
|||
this.dataType = dataType; |
|||
return this; |
|||
} |
|||
|
|||
public MetricBuilder isHistorical(Boolean isHistorical) { |
|||
this.isHistorical = isHistorical; |
|||
return this; |
|||
} |
|||
|
|||
public MetricBuilder isTransient(Boolean isTransient) { |
|||
this.isTransient = isTransient; |
|||
return this; |
|||
} |
|||
|
|||
public MetricBuilder metaData(MetaData metaData) { |
|||
this.metaData = metaData; |
|||
return this; |
|||
} |
|||
|
|||
public MetricBuilder properties(PropertySet properties) { |
|||
this.properties = properties; |
|||
return this; |
|||
} |
|||
|
|||
public MetricBuilder value(Object value) { |
|||
this.value = value; |
|||
return this; |
|||
} |
|||
|
|||
public Metric createMetric() throws Exception { |
|||
return new Metric(name, alias, timestamp, dataType, isHistorical, isTransient, metaData, |
|||
properties, value); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,138 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.message; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
|
|||
import java.math.BigInteger; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* An enumeration of data types associated with the value of a {@link Metric} |
|||
*/ |
|||
@Slf4j |
|||
public enum MetricDataType { |
|||
|
|||
// Basic Types
|
|||
Int8(1, Byte.class), |
|||
Int16(2, Short.class), |
|||
Int32(3, Integer.class), |
|||
Int64(4, Long.class), |
|||
UInt8(5, Short.class), |
|||
UInt16(6, Integer.class), |
|||
UInt32(7, Long.class), |
|||
UInt64(8, BigInteger.class), |
|||
Float(9, Float.class), |
|||
Double(10, Double.class), |
|||
Boolean(11, Boolean.class), |
|||
String(12, String.class), |
|||
DateTime(13, Date.class), |
|||
Text(14, String.class), |
|||
|
|||
// Custom Types for Metrics
|
|||
UUID(15, String.class), |
|||
DataSet(16, DataSet.class), |
|||
Bytes(17, byte[].class), |
|||
File(18, File.class), |
|||
Template(19, Template.class), |
|||
|
|||
// Unknown
|
|||
Unknown(0, Object.class); |
|||
|
|||
private Class<?> clazz = null; |
|||
private int intValue = 0; |
|||
|
|||
private MetricDataType(int intValue, Class<?> clazz) { |
|||
this.intValue = intValue; |
|||
this.clazz = clazz; |
|||
} |
|||
|
|||
public void checkType(Object value) throws Exception { |
|||
if (value != null && !value.getClass().equals(clazz)) { |
|||
log.warn("Failed type check - " + clazz + " != " + ((value != null) ? value.getClass().toString() : "null")); |
|||
throw new Exception(value.getClass().getName()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Returns an integer representation of the data type. |
|||
* |
|||
* @return an integer representation of the data type. |
|||
*/ |
|||
public int toIntValue() { |
|||
return this.intValue; |
|||
} |
|||
|
|||
/** |
|||
* Converts the integer representation of the data type into a {@link MetricDataType} instance. |
|||
* |
|||
* @param i the integer representation of the data type. |
|||
* @return a {@link MetricDataType} instance. |
|||
*/ |
|||
public static MetricDataType fromInteger(int i) { |
|||
switch(i) { |
|||
case 1: |
|||
return Int8; |
|||
case 2: |
|||
return Int16; |
|||
case 3: |
|||
return Int32; |
|||
case 4: |
|||
return Int64; |
|||
case 5: |
|||
return UInt8; |
|||
case 6: |
|||
return UInt16; |
|||
case 7: |
|||
return UInt32; |
|||
case 8: |
|||
return UInt64; |
|||
case 9: |
|||
return Float; |
|||
case 10: |
|||
return Double; |
|||
case 11: |
|||
return Boolean; |
|||
case 12: |
|||
return String; |
|||
case 13: |
|||
return DateTime; |
|||
case 14: |
|||
return Text; |
|||
case 15: |
|||
return UUID; |
|||
case 16: |
|||
return DataSet; |
|||
case 17: |
|||
return Bytes; |
|||
case 18: |
|||
return File; |
|||
case 19: |
|||
return Template; |
|||
default: |
|||
return Unknown; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Returns the class type for this DataType |
|||
* |
|||
* @return the class type for this DataType |
|||
*/ |
|||
public Class<?> getClazz() { |
|||
return clazz; |
|||
} |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.message; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonGetter; |
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
import com.fasterxml.jackson.annotation.JsonSetter; |
|||
|
|||
import java.util.Objects; |
|||
|
|||
/** |
|||
* A class to represent a parameter associated with a template. |
|||
*/ |
|||
public class Parameter { |
|||
|
|||
/** |
|||
* The name of the parameter |
|||
*/ |
|||
@JsonProperty("name") |
|||
private String name; |
|||
|
|||
/** |
|||
* The data type of the parameter |
|||
*/ |
|||
@JsonProperty("type") |
|||
private ParameterDataType type; |
|||
|
|||
/** |
|||
* The value of the parameter |
|||
*/ |
|||
@JsonProperty("value") |
|||
private Object value; |
|||
|
|||
|
|||
/** |
|||
* Constructs a Parameter instance. |
|||
* |
|||
* @param name The name of the parameter. |
|||
* @param type The type of the parameter. |
|||
* @param value The value of the parameter. |
|||
* @throws Exception |
|||
*/ |
|||
public Parameter(String name, ParameterDataType type, Object value) throws Exception { |
|||
this.name = name; |
|||
this.type = type; |
|||
this.value = value; |
|||
this.type.checkType(value); |
|||
} |
|||
|
|||
@JsonGetter("name") |
|||
public String getName() { |
|||
return name; |
|||
} |
|||
|
|||
@JsonSetter("name") |
|||
public void setName(String name) { |
|||
this.name = name; |
|||
} |
|||
|
|||
public ParameterDataType getType() { |
|||
return type; |
|||
} |
|||
|
|||
public void setType(ParameterDataType type) { |
|||
this.type = type; |
|||
} |
|||
|
|||
public Object getValue() { |
|||
return value; |
|||
} |
|||
|
|||
public void setValue(Object value) { |
|||
this.value = value; |
|||
} |
|||
|
|||
@Override |
|||
public boolean equals(Object object) { |
|||
if (this == object) { |
|||
return true; |
|||
} |
|||
if (object == null || this.getClass() != object.getClass()) { |
|||
return false; |
|||
} |
|||
Parameter param = (Parameter) object; |
|||
return Objects.equals(name, param.getName()) |
|||
&& Objects.equals(type, param.getType()) |
|||
&& Objects.equals(value, param.getValue()); |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "Parameter [name=" + name + ", type=" + type + ", value=" + value + "]"; |
|||
} |
|||
} |
|||
@ -0,0 +1,125 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.message; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.log4j.LogManager; |
|||
import org.apache.log4j.Logger; |
|||
|
|||
import java.math.BigInteger; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* An enumeration of data types for the value of a {@link Parameter} for a {@link Template} |
|||
*/ |
|||
@Slf4j |
|||
public enum ParameterDataType { |
|||
|
|||
// Basic Types
|
|||
Int8(1, Byte.class), |
|||
Int16(2, Short.class), |
|||
Int32(3, Integer.class), |
|||
Int64(4, Long.class), |
|||
UInt8(5, Short.class), |
|||
UInt16(6, Integer.class), |
|||
UInt32(7, Long.class), |
|||
UInt64(8, BigInteger.class), |
|||
Float(9, Float.class), |
|||
Double(10, Double.class), |
|||
Boolean(11, Boolean.class), |
|||
String(12, String.class), |
|||
DateTime(13, Date.class), |
|||
Text(14, String.class), |
|||
|
|||
// Unknown
|
|||
Unknown(0, Object.class); |
|||
|
|||
private static Logger logger = LogManager.getLogger(ParameterDataType.class.getName()); |
|||
|
|||
private Class<?> clazz = null; |
|||
private int intValue = 0; |
|||
|
|||
private ParameterDataType(int intValue, Class<?> clazz) { |
|||
this.intValue = intValue; |
|||
this.clazz = clazz; |
|||
} |
|||
|
|||
public void checkType(Object value) throws Exception { |
|||
if (value != null && !value.getClass().equals(clazz)) { |
|||
log.warn("Failed type check - " + clazz + " != " + value.getClass().toString()); |
|||
throw new Exception(value.getClass().getName()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Returns an integer representation of the data type. |
|||
* |
|||
* @return an integer representation of the data type. |
|||
*/ |
|||
public int toIntValue() { |
|||
return this.intValue; |
|||
} |
|||
|
|||
/** |
|||
* Converts the integer representation of the data type into a {@link ParameterDataType} instance. |
|||
* |
|||
* @param i the integer representation of the data type. |
|||
* @return a {@link ParameterDataType} instance. |
|||
*/ |
|||
public static ParameterDataType fromInteger(int i) { |
|||
switch(i) { |
|||
case 1: |
|||
return Int8; |
|||
case 2: |
|||
return Int16; |
|||
case 3: |
|||
return Int32; |
|||
case 4: |
|||
return Int64; |
|||
case 5: |
|||
return UInt8; |
|||
case 6: |
|||
return UInt16; |
|||
case 7: |
|||
return UInt32; |
|||
case 8: |
|||
return UInt64; |
|||
case 9: |
|||
return Float; |
|||
case 10: |
|||
return Double; |
|||
case 11: |
|||
return Boolean; |
|||
case 12: |
|||
return String; |
|||
case 13: |
|||
return DateTime; |
|||
case 14: |
|||
return Text; |
|||
default: |
|||
return Unknown; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Returns the class type for this DataType |
|||
* |
|||
* @return the class type for this DataType |
|||
*/ |
|||
public Class<?> getClazz() { |
|||
return clazz; |
|||
} |
|||
} |
|||
@ -0,0 +1,134 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.message; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
|
|||
import java.math.BigInteger; |
|||
import java.util.Date; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* An enumeration of data types for values of a {@link PropertySet} |
|||
*/ |
|||
@Slf4j |
|||
public enum PropertyDataType { |
|||
|
|||
// Basic Types
|
|||
Int8(1, Byte.class), |
|||
Int16(2, Short.class), |
|||
Int32(3, Integer.class), |
|||
Int64(4, Long.class), |
|||
UInt8(5, Short.class), |
|||
UInt16(6, Integer.class), |
|||
UInt32(7, Long.class), |
|||
UInt64(8, BigInteger.class), |
|||
Float(9, Float.class), |
|||
Double(10, Double.class), |
|||
Boolean(11, Boolean.class), |
|||
String(12, String.class), |
|||
DateTime(13, Date.class), |
|||
Text(14, String.class), |
|||
|
|||
// Custom Types for PropertySets
|
|||
PropertySet(20, PropertySet.class), |
|||
PropertySetList(21, List.class), |
|||
|
|||
// Unknown
|
|||
Unknown(0, Object.class); |
|||
|
|||
private Class<?> clazz = null; |
|||
private int intValue = 0; |
|||
|
|||
private PropertyDataType(int intValue, Class<?> clazz) { |
|||
this.intValue = intValue; |
|||
this.clazz = clazz; |
|||
} |
|||
|
|||
public void checkType(Object value) throws Exception { |
|||
if (value != null && !value.getClass().equals(clazz)) { |
|||
if(clazz == List.class && value instanceof List) { |
|||
// Allow List subclasses
|
|||
} else { |
|||
log.warn("Failed type check - " + clazz + " != " + value.getClass().toString()); |
|||
throw new Exception(value.getClass().getName()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Returns an integer representation of the data type. |
|||
* |
|||
* @return an integer representation of the data type. |
|||
*/ |
|||
public int toIntValue() { |
|||
return this.intValue; |
|||
} |
|||
|
|||
/** |
|||
* Converts the integer representation of the data type into a {@link PropertyDataType} instance. |
|||
* |
|||
* @param i the integer representation of the data type. |
|||
* @return a {@link PropertyDataType} instance. |
|||
*/ |
|||
public static PropertyDataType fromInteger(int i) { |
|||
switch(i) { |
|||
case 1: |
|||
return Int8; |
|||
case 2: |
|||
return Int16; |
|||
case 3: |
|||
return Int32; |
|||
case 4: |
|||
return Int64; |
|||
case 5: |
|||
return UInt8; |
|||
case 6: |
|||
return UInt16; |
|||
case 7: |
|||
return UInt32; |
|||
case 8: |
|||
return UInt64; |
|||
case 9: |
|||
return Float; |
|||
case 10: |
|||
return Double; |
|||
case 11: |
|||
return Boolean; |
|||
case 12: |
|||
return String; |
|||
case 13: |
|||
return DateTime; |
|||
case 14: |
|||
return Text; |
|||
case 20: |
|||
return PropertySet; |
|||
case 21: |
|||
return PropertySetList; |
|||
default: |
|||
return Unknown; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Returns the class type for this DataType |
|||
* |
|||
* @return the class type for this DataType |
|||
*/ |
|||
public Class<?> getClazz() { |
|||
return clazz; |
|||
} |
|||
} |
|||
@ -0,0 +1,169 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.message; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore; |
|||
|
|||
import java.util.Collection; |
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
|
|||
/** |
|||
* A class that maintains a set of properties associated with a {@link Metric}. |
|||
*/ |
|||
public class PropertySet implements Map<String, PropertyValue> { |
|||
|
|||
@JsonIgnore |
|||
private Map<String, PropertyValue> map; |
|||
|
|||
public PropertySet() { |
|||
this.map = new HashMap<String, PropertyValue>(); |
|||
} |
|||
|
|||
private PropertySet(Map<String, PropertyValue> propertyMap) { |
|||
this.map = propertyMap; |
|||
} |
|||
|
|||
@JsonIgnore |
|||
public PropertyValue getPropertyValue(String name) { |
|||
return this.map.get(name); |
|||
} |
|||
|
|||
@JsonIgnore |
|||
public void setProperty(String name, PropertyValue value) { |
|||
this.map.put(name, value); |
|||
} |
|||
|
|||
@JsonIgnore |
|||
public void removeProperty(String name) { |
|||
this.map.remove(name); |
|||
} |
|||
|
|||
@JsonIgnore |
|||
public void clear() { |
|||
this.map.clear(); |
|||
} |
|||
|
|||
@JsonIgnore |
|||
public Set<String> getNames() { |
|||
return map.keySet(); |
|||
} |
|||
|
|||
@JsonIgnore |
|||
public Collection<PropertyValue> getValues() { |
|||
return map.values(); |
|||
} |
|||
|
|||
@JsonIgnore |
|||
public Map<String, PropertyValue> getPropertyMap() { |
|||
return map; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "PropertySet [propertyMap=" + map + "]"; |
|||
} |
|||
|
|||
@Override |
|||
public int size() { |
|||
return map.size(); |
|||
} |
|||
|
|||
@Override |
|||
public boolean isEmpty() { |
|||
return map.isEmpty(); |
|||
} |
|||
|
|||
@Override |
|||
public boolean containsKey(Object key) { |
|||
return map.containsKey(key); |
|||
} |
|||
|
|||
@Override |
|||
public boolean containsValue(Object value) { |
|||
return map.containsValue(value); |
|||
} |
|||
|
|||
@Override |
|||
public PropertyValue get(Object key) { |
|||
return map.get(key); |
|||
} |
|||
|
|||
@Override |
|||
public PropertyValue put(String key, PropertyValue value) { |
|||
return map.put(key, value); |
|||
} |
|||
|
|||
@Override |
|||
public PropertyValue remove(Object key) { |
|||
return map.remove(key); |
|||
} |
|||
|
|||
@Override |
|||
public void putAll(Map<? extends String, ? extends PropertyValue> m) { |
|||
map.putAll(m); |
|||
} |
|||
|
|||
@Override |
|||
public Set<String> keySet() { |
|||
return map.keySet(); |
|||
} |
|||
|
|||
@Override |
|||
public Collection<PropertyValue> values() { |
|||
return map.values(); |
|||
} |
|||
|
|||
@Override |
|||
public Set<Entry<String, PropertyValue>> entrySet() { |
|||
return map.entrySet(); |
|||
} |
|||
|
|||
/** |
|||
* A builder for a PropertySet instance |
|||
*/ |
|||
public static class PropertySetBuilder { |
|||
|
|||
private Map<String, PropertyValue> propertyMap; |
|||
|
|||
public PropertySetBuilder() { |
|||
this.propertyMap = new HashMap<String, PropertyValue>(); |
|||
} |
|||
|
|||
public PropertySetBuilder(PropertySet propertySet) throws Exception { |
|||
this.propertyMap = new HashMap<String, PropertyValue>(); |
|||
for (String name : propertySet.getNames()) { |
|||
PropertyValue value = propertySet.getPropertyValue(name); |
|||
propertyMap.put(name, new PropertyValue(value.getType(), value.getValue())); |
|||
} |
|||
} |
|||
|
|||
public PropertySetBuilder addProperty(String name, PropertyValue value) { |
|||
this.propertyMap.put(name, value); |
|||
return this; |
|||
} |
|||
|
|||
public PropertySetBuilder addProperties(Map<String, PropertyValue> properties) { |
|||
this.propertyMap.putAll(properties); |
|||
return this; |
|||
} |
|||
|
|||
public PropertySet createPropertySet() { |
|||
return new PropertySet(propertyMap); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.message; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore; |
|||
|
|||
import java.util.Objects; |
|||
|
|||
/** |
|||
* The value of a property in a {@link PropertySet}. |
|||
*/ |
|||
public class PropertyValue { |
|||
|
|||
private PropertyDataType type; |
|||
private Object value; |
|||
private Boolean isNull = null; |
|||
|
|||
public PropertyValue() {} |
|||
|
|||
/** |
|||
* A constructor. |
|||
* |
|||
* @param type the property type |
|||
* @param value the property value |
|||
* @throws Exception |
|||
*/ |
|||
public PropertyValue(PropertyDataType type, Object value) throws Exception { |
|||
this.type = type; |
|||
this.value = value; |
|||
isNull = (value == null) ? true : false; |
|||
type.checkType(value); |
|||
} |
|||
|
|||
public PropertyDataType getType() { |
|||
return type; |
|||
} |
|||
|
|||
public void setType(PropertyDataType type) { |
|||
this.type = type; |
|||
} |
|||
|
|||
public Object getValue() { |
|||
return value; |
|||
} |
|||
|
|||
public void setValue(Object value) { |
|||
this.value = value; |
|||
isNull = (value == null) ? true : false; |
|||
} |
|||
|
|||
@JsonIgnore |
|||
public Boolean isNull() { |
|||
return isNull; |
|||
} |
|||
|
|||
@Override |
|||
public boolean equals(Object object) { |
|||
if (this == object) { |
|||
return true; |
|||
} |
|||
if (object == null || this.getClass() != object.getClass()) { |
|||
return false; |
|||
} |
|||
PropertyValue propValue = (PropertyValue) object; |
|||
return Objects.equals(type, propValue.getType()) |
|||
&& Objects.equals(value, propValue.getValue()); |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "PropertyValue [type=" + type + ", value=" + value + ", isNull=" + isNull + "]"; |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.message; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Collection; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* A class for representing a row of a data set. |
|||
*/ |
|||
public class Row { |
|||
|
|||
private List<SparkplugValue<?>> values; |
|||
|
|||
public Row(List<SparkplugValue<?>> values) { |
|||
this.values = values; |
|||
} |
|||
|
|||
public List<SparkplugValue<?>> getValues() { |
|||
return values; |
|||
} |
|||
|
|||
public void setValues(List<SparkplugValue<?>> values) { |
|||
this.values = values; |
|||
} |
|||
|
|||
public void addValue(SparkplugValue<?> value) { |
|||
this.values.add(value); |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "Row [values=" + values + "]"; |
|||
} |
|||
|
|||
/** |
|||
* Converts a {@link Row} instance to a {@link List} of Objects representing the values. |
|||
* |
|||
* @param row a {@link Row} instance. |
|||
* @return a {@link List} of Objects. |
|||
*/ |
|||
public static List<Object> toValues(Row row) { |
|||
List<Object> list = new ArrayList<Object>(row.getValues().size()); |
|||
for (SparkplugValue<?> value : row.getValues()) { |
|||
list.add(value.getValue()); |
|||
} |
|||
return list; |
|||
} |
|||
|
|||
/** |
|||
* A builder for creating a {@link Row} instance. |
|||
*/ |
|||
public static class RowBuilder { |
|||
|
|||
private List<SparkplugValue<?>> values; |
|||
|
|||
public RowBuilder() { |
|||
this.values = new ArrayList<SparkplugValue<?>>(); |
|||
} |
|||
|
|||
public RowBuilder(Row row) { |
|||
this.values = new ArrayList<SparkplugValue<?>>(row.getValues()); |
|||
} |
|||
|
|||
public RowBuilder addValue(SparkplugValue<?> value) { |
|||
this.values.add(value); |
|||
return this; |
|||
} |
|||
|
|||
public RowBuilder addValues(Collection<SparkplugValue<?>> values) { |
|||
this.values.addAll(values); |
|||
return this; |
|||
} |
|||
|
|||
public Row createRow() { |
|||
return new Row(values); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,161 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.message; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonInclude; |
|||
import org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugMessageType; |
|||
|
|||
/** |
|||
* Created by nickAS21 on 12.12.22 |
|||
*/ |
|||
@JsonInclude(JsonInclude.Include.NON_NULL) |
|||
public class SparkplugTopic { |
|||
|
|||
/** |
|||
* The Sparkplug namespace version. |
|||
* For the Sparkplug™ A version of the payload definition, the UTF-8 string constant for the namespace element will be: |
|||
* “spAv1.0” |
|||
* For the Sparkplug™ B version of the specification, the UTF-8 string constant for the namespace element will be: |
|||
* “spBv1.0” |
|||
*/ |
|||
private String namespace; |
|||
|
|||
/** |
|||
* The ID of the logical grouping of Edge of Network (EoN) Nodes and devices. |
|||
*/ |
|||
private String groupId; |
|||
|
|||
/** |
|||
* The ID of the Edge of Network (EoN) Node. |
|||
*/ |
|||
private String edgeNodeId; |
|||
|
|||
/** |
|||
* The ID of the device. |
|||
*/ |
|||
private String deviceId; |
|||
|
|||
/** |
|||
* The message type. |
|||
*/ |
|||
private SparkplugMessageType type; |
|||
|
|||
/** |
|||
* Constructor (device). |
|||
* |
|||
* @param namespace the namespace. |
|||
* @param groupId the group ID. |
|||
* @param edgeNodeId the edge node ID. |
|||
* @param deviceId the device ID. |
|||
* @param type the message type. |
|||
*/ |
|||
public SparkplugTopic(String namespace, String groupId, String edgeNodeId, String deviceId, SparkplugMessageType type) { |
|||
super(); |
|||
this.namespace = namespace; |
|||
this.groupId = groupId; |
|||
this.edgeNodeId = edgeNodeId; |
|||
this.deviceId = deviceId; |
|||
this.type = type; |
|||
} |
|||
|
|||
/** |
|||
* Constructor (node). |
|||
* |
|||
* @param namespace the namespace. |
|||
* @param groupId the group ID. |
|||
* @param edgeNodeId the edge node ID. |
|||
* @param type the message type. |
|||
*/ |
|||
public SparkplugTopic(String namespace, String groupId, String edgeNodeId, SparkplugMessageType type) { |
|||
super(); |
|||
this.namespace = namespace; |
|||
this.groupId = groupId; |
|||
this.edgeNodeId = edgeNodeId; |
|||
this.deviceId = null; |
|||
this.type = type; |
|||
} |
|||
|
|||
/** |
|||
* Returns the Sparkplug namespace version. |
|||
* |
|||
* @return the namespace |
|||
*/ |
|||
public String getNamespace() { |
|||
return namespace; |
|||
} |
|||
|
|||
/** |
|||
* Returns the ID of the logical grouping of Edge of Network (EoN) Nodes and devices. |
|||
* |
|||
* @return the group ID |
|||
*/ |
|||
public String getGroupId() { |
|||
return groupId; |
|||
} |
|||
|
|||
/** |
|||
* Returns the ID of the Edge of Network (EoN) Node. |
|||
* |
|||
* @return the edge node ID |
|||
*/ |
|||
public String getEdgeNodeId() { |
|||
return edgeNodeId; |
|||
} |
|||
|
|||
/** |
|||
* Returns the ID of the device. |
|||
* |
|||
* @return the device ID |
|||
*/ |
|||
public String getDeviceId() { |
|||
return deviceId; |
|||
} |
|||
|
|||
/** |
|||
* Returns the message type. |
|||
* |
|||
* @return the message type |
|||
*/ |
|||
public SparkplugMessageType getType() { |
|||
return type; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
StringBuilder sb = new StringBuilder(getNamespace()).append("/") |
|||
.append(getGroupId()).append("/") |
|||
.append(getType()).append("/") |
|||
.append(getEdgeNodeId()); |
|||
if (getDeviceId() != null) { |
|||
sb.append("/").append(getDeviceId()); |
|||
} |
|||
return sb.toString(); |
|||
} |
|||
|
|||
/** |
|||
* Returns true if this topic's type matches the passes in type, false otherwise. |
|||
* |
|||
* @param type the type to check |
|||
* @return true if this topic's type matches the passes in type, false otherwise |
|||
*/ |
|||
public boolean isType(SparkplugMessageType type) { |
|||
return this.type != null && this.type.equals(type); |
|||
} |
|||
|
|||
public boolean isNode() { |
|||
return this.deviceId == null; |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.message; |
|||
|
|||
public class SparkplugValue<V> { |
|||
|
|||
private DataSetDataType type; |
|||
private V value; |
|||
|
|||
public SparkplugValue() { |
|||
super(); |
|||
} |
|||
|
|||
public SparkplugValue(DataSetDataType type, V value) { |
|||
super(); |
|||
this.type = type; |
|||
this.value = value; |
|||
} |
|||
|
|||
public DataSetDataType getType() { |
|||
return type; |
|||
} |
|||
|
|||
public void setType(DataSetDataType type) { |
|||
this.type = type; |
|||
} |
|||
|
|||
public V getValue() { |
|||
return value; |
|||
} |
|||
|
|||
public void setValue(V value) { |
|||
this.value = value; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "Value [type=" + type + ", value=" + value + "]"; |
|||
} |
|||
} |
|||
@ -0,0 +1,202 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.transport.mqtt.util.sparkplug.message; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonGetter; |
|||
import com.fasterxml.jackson.annotation.JsonInclude; |
|||
import com.fasterxml.jackson.annotation.JsonInclude.Include; |
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
import com.fasterxml.jackson.annotation.JsonSetter; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Collection; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* A class representing a template associated with a metric |
|||
*/ |
|||
@JsonInclude(Include.NON_NULL) |
|||
public class Template { |
|||
|
|||
/** |
|||
* The Template version. |
|||
*/ |
|||
@JsonProperty("version") |
|||
private String version; |
|||
|
|||
/** |
|||
* The template reference |
|||
*/ |
|||
@JsonProperty("reference") |
|||
private String templateRef; |
|||
|
|||
/** |
|||
* True if the template is a definition, false otherwise. |
|||
*/ |
|||
@JsonProperty("isDefinition") |
|||
private boolean isDefinition; |
|||
|
|||
/** |
|||
* List of metrics. |
|||
*/ |
|||
@JsonProperty("metrics") |
|||
private List<Metric> metrics; |
|||
|
|||
/** |
|||
* List of parameters. |
|||
*/ |
|||
@JsonProperty("parameters") |
|||
@JsonInclude(Include.NON_EMPTY) |
|||
private List<Parameter> parameters; |
|||
|
|||
public Template() {} |
|||
|
|||
public Template(String version, String templateRef, boolean isDefinition, List<Metric> metrics, |
|||
List<Parameter> parameters) { |
|||
this.version = version; |
|||
this.templateRef = templateRef; |
|||
this.isDefinition = isDefinition; |
|||
this.metrics = metrics; |
|||
this.parameters = parameters; |
|||
} |
|||
|
|||
public String getVersion() { |
|||
return version; |
|||
} |
|||
|
|||
public void setVersion(String version) { |
|||
this.version = version; |
|||
} |
|||
|
|||
public String getTemplateRef() { |
|||
return templateRef; |
|||
} |
|||
|
|||
public void setTemplateRef(String templateRef) { |
|||
this.templateRef = templateRef; |
|||
} |
|||
|
|||
@JsonGetter("isDefinition") |
|||
public boolean isDefinition() { |
|||
return isDefinition; |
|||
} |
|||
|
|||
@JsonSetter("isDefinition") |
|||
public void setDefinition(boolean isDefinition) { |
|||
this.isDefinition = isDefinition; |
|||
} |
|||
|
|||
public List<Metric> getMetrics() { |
|||
return metrics; |
|||
} |
|||
|
|||
public void setMetrics(List<Metric> metrics) { |
|||
this.metrics = metrics; |
|||
} |
|||
|
|||
public void addMetric(Metric metric) { |
|||
this.metrics.add(metric); |
|||
} |
|||
|
|||
public List<Parameter> getParameters() { |
|||
return parameters; |
|||
} |
|||
|
|||
public void setParameters(List<Parameter> parameters) { |
|||
this.parameters = parameters; |
|||
} |
|||
|
|||
public void addParameter(Parameter parameter) { |
|||
this.parameters.add(parameter); |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "Template [version=" + version + ", templateRef=" + templateRef + ", isDefinition=" + isDefinition |
|||
+ ", metrics=" + metrics + ", parameters=" + parameters + "]"; |
|||
} |
|||
|
|||
/** |
|||
* A builder for creating a {@link Template} instance. |
|||
*/ |
|||
public static class TemplateBuilder { |
|||
|
|||
private String version; |
|||
private String templateRef; |
|||
private boolean isDefinition; |
|||
private List<Metric> metrics; |
|||
private List<Parameter> parameters; |
|||
|
|||
public TemplateBuilder() { |
|||
super(); |
|||
this.metrics = new ArrayList<Metric>(); |
|||
this.parameters = new ArrayList<Parameter>(); |
|||
} |
|||
|
|||
public TemplateBuilder(Template template) throws Exception { |
|||
this.version = template.getVersion(); |
|||
this.templateRef = template.getTemplateRef(); |
|||
this.isDefinition = template.isDefinition(); |
|||
this.metrics = new ArrayList<Metric>(template.getMetrics().size()); |
|||
for (Metric metric : template.getMetrics()) { |
|||
this.metrics.add(new Metric.MetricBuilder(metric).createMetric()); |
|||
} |
|||
this.parameters = new ArrayList<Parameter>(template.getParameters().size()); |
|||
for (Parameter parameter : template.getParameters()) { |
|||
this.parameters.add(new Parameter(parameter.getName(), parameter.getType(), parameter.getValue())); |
|||
} |
|||
} |
|||
|
|||
public TemplateBuilder version(String version) { |
|||
this.version = version; |
|||
return this; |
|||
} |
|||
|
|||
public TemplateBuilder templateRef(String templateRef) { |
|||
this.templateRef = templateRef; |
|||
return this; |
|||
} |
|||
|
|||
public TemplateBuilder definition(boolean isDefinition) { |
|||
this.isDefinition = isDefinition; |
|||
return this; |
|||
} |
|||
|
|||
public TemplateBuilder addMetric(Metric metric) { |
|||
this.metrics.add(metric); |
|||
return this; |
|||
} |
|||
|
|||
public TemplateBuilder addMetrics(Collection<Metric> metrics) { |
|||
this.metrics.addAll(metrics); |
|||
return this; |
|||
} |
|||
|
|||
public TemplateBuilder addParameter(Parameter parameter) { |
|||
this.parameters.add(parameter); |
|||
return this; |
|||
} |
|||
|
|||
public TemplateBuilder addParameters(Collection<Parameter> parameters) { |
|||
this.parameters.addAll(parameters); |
|||
return this; |
|||
} |
|||
|
|||
public Template createTemplate() { |
|||
return new Template(version, templateRef, isDefinition, metrics, parameters); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed 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 |
|||
* |
|||
* http://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 org.thingsboard.server.common.transport.auth; |
|||
|
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
|
|||
@Data |
|||
@Builder |
|||
public class GetOrCreateDeviceFromSparkplugResponse implements DeviceProfileAware { |
|||
|
|||
private TransportDeviceInfo deviceInfo; |
|||
private DeviceProfile deviceProfile; |
|||
|
|||
} |
|||
Loading…
Reference in new issue