Browse Source

Merge branch 'thingsboard:master' into fix/3806-opc-ua-extesion-fixes

pull/11173/head
Max Petrov 2 years ago
committed by GitHub
parent
commit
d46bf6d0e0
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      application/src/main/data/json/system/widget_types/update_server_string_attribute.json
  2. 2
      application/src/main/data/json/system/widget_types/update_shared_string_attribute.json
  3. 2
      application/src/main/data/json/system/widget_types/update_string_timeseries.json
  4. 1
      application/src/test/java/org/thingsboard/server/edge/NotificationEdgeTest.java
  5. 7
      application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java
  6. 2
      common/cache/src/main/java/org/thingsboard/server/cache/CacheSpecsMap.java
  7. 39
      common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java
  8. 34
      common/cache/src/main/java/org/thingsboard/server/cache/TbTransactionalCache.java
  9. 3
      common/cache/src/test/java/org/thingsboard/server/cache/CacheSpecsMapTest.java
  10. 4
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java
  11. 4
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java
  12. 516
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java
  13. 302
      common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java
  14. 15
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  15. 82
      dao/src/test/java/org/thingsboard/server/dao/cache/RedisTbTransactionalCacheTest.java
  16. 87
      dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java
  17. 24
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java
  18. 2
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java
  19. 4
      ui-ngx/src/app/core/services/utils.service.ts
  20. 2
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html
  21. 24
      ui-ngx/src/app/modules/home/components/widget/lib/cards/unread-notification-widget.component.ts
  22. 2
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts
  23. 14
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts
  24. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html
  25. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-bar-settings.component.html
  26. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.html
  27. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.ts
  28. 1
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  29. 2
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  30. 49
      ui-ngx/src/app/shared/components/entity/entity-list.component.ts

2
application/src/main/data/json/system/widget_types/update_server_string_attribute.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/update_shared_string_attribute.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/update_string_timeseries.json

File diff suppressed because one or more lines are too long

1
application/src/test/java/org/thingsboard/server/edge/NotificationEdgeTest.java

@ -18,7 +18,6 @@ package org.thingsboard.server.edge;
import com.google.protobuf.AbstractMessage;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;

7
application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java

@ -59,12 +59,7 @@ public class OAuth2EdgeTest extends AbstractEdgeTest {
oAuth2Info.setEnabled(false);
oAuth2Info.setEdgeEnabled(false);
doPost("/api/oauth2/config", oAuth2Info, OAuth2Info.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof OAuth2UpdateMsg);
oAuth2UpdateMsg = (OAuth2UpdateMsg) latestMessage;
result = JacksonUtil.fromString(oAuth2UpdateMsg.getEntity(), OAuth2Info.class, true);
Assert.assertEquals(oAuth2Info, result);
Assert.assertFalse(edgeImitator.waitForMessages(5));
edgeImitator.ignoreType(OAuth2UpdateMsg.class);
loginTenantAdmin();

2
common/cache/src/main/java/org/thingsboard/server/cache/CacheSpecsMap.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.cache;
import jakarta.annotation.PostConstruct;
import lombok.Data;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
@ -22,7 +23,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.thingsboard.server.common.data.CacheConstants;
import jakarta.annotation.PostConstruct;
import java.util.Map;
@Configuration

39
common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java

@ -29,7 +29,6 @@ import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.thingsboard.server.common.data.FstStatsService;
import redis.clients.jedis.Connection;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.util.JedisClusterCRC16;
@ -40,6 +39,8 @@ import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
@Slf4j
public abstract class RedisTbTransactionalCache<K extends Serializable, V extends Serializable> implements TbTransactionalCache<K, V> {
@ -57,6 +58,7 @@ public abstract class RedisTbTransactionalCache<K extends Serializable, V extend
private final TbRedisSerializer<K, V> valueSerializer;
private final Expiration evictExpiration;
private final Expiration cacheTtl;
private final boolean cacheEnabled;
public RedisTbTransactionalCache(String cacheName,
CacheSpecsMap cacheSpecsMap,
@ -73,10 +75,19 @@ public abstract class RedisTbTransactionalCache<K extends Serializable, V extend
.map(CacheSpecs::getTimeToLiveInMinutes)
.map(t -> Expiration.from(t, TimeUnit.MINUTES))
.orElseGet(Expiration::persistent);
this.cacheEnabled = Optional.ofNullable(cacheSpecsMap)
.map(CacheSpecsMap::getSpecs)
.map(x -> x.get(cacheName))
.map(CacheSpecs::getMaxSize)
.map(size -> size > 0)
.orElse(false);
}
@Override
public TbCacheValueWrapper<V> get(K key) {
if (!cacheEnabled) {
return null;
}
try (var connection = connectionFactory.getConnection()) {
byte[] rawKey = getRawKey(key);
byte[] rawValue = connection.get(rawKey);
@ -98,6 +109,9 @@ public abstract class RedisTbTransactionalCache<K extends Serializable, V extend
@Override
public void put(K key, V value) {
if (!cacheEnabled) {
return;
}
try (var connection = connectionFactory.getConnection()) {
put(connection, key, value, RedisStringCommands.SetOption.UPSERT);
}
@ -105,6 +119,9 @@ public abstract class RedisTbTransactionalCache<K extends Serializable, V extend
@Override
public void putIfAbsent(K key, V value) {
if (!cacheEnabled) {
return;
}
try (var connection = connectionFactory.getConnection()) {
put(connection, key, value, RedisStringCommands.SetOption.SET_IF_ABSENT);
}
@ -112,6 +129,9 @@ public abstract class RedisTbTransactionalCache<K extends Serializable, V extend
@Override
public void evict(K key) {
if (!cacheEnabled) {
return;
}
try (var connection = connectionFactory.getConnection()) {
connection.del(getRawKey(key));
}
@ -119,6 +139,9 @@ public abstract class RedisTbTransactionalCache<K extends Serializable, V extend
@Override
public void evict(Collection<K> keys) {
if (!cacheEnabled) {
return;
}
//Redis expects at least 1 key to delete. Otherwise - ERR wrong number of arguments for 'del' command
if (keys.isEmpty()) {
return;
@ -130,6 +153,9 @@ public abstract class RedisTbTransactionalCache<K extends Serializable, V extend
@Override
public void evictOrPut(K key, V value) {
if (!cacheEnabled) {
return;
}
try (var connection = connectionFactory.getConnection()) {
var rawKey = getRawKey(key);
var records = connection.del(rawKey);
@ -153,6 +179,14 @@ public abstract class RedisTbTransactionalCache<K extends Serializable, V extend
return new RedisTbCacheTransaction<>(this, connection);
}
@Override
public <R> R getAndPutInTransaction(K key, Supplier<R> dbCall, Function<V, R> cacheValueToResult, Function<R, V> dbValueToCacheValue, boolean cacheNullValue) {
if (!cacheEnabled) {
return dbCall.get();
}
return TbTransactionalCache.super.getAndPutInTransaction(key, dbCall, cacheValueToResult, dbValueToCacheValue, cacheNullValue);
}
private RedisConnection getConnection(byte[] rawKey) {
if (!connectionFactory.isRedisClusterAware()) {
return connectionFactory.getConnection();
@ -214,6 +248,9 @@ public abstract class RedisTbTransactionalCache<K extends Serializable, V extend
}
public void put(RedisConnection connection, K key, V value, RedisStringCommands.SetOption setOption) {
if (!cacheEnabled) {
return;
}
byte[] rawKey = getRawKey(key);
byte[] rawValue = getRawValue(value);
connection.set(rawKey, rawValue, cacheTtl, setOption);

34
common/cache/src/main/java/org/thingsboard/server/cache/TbTransactionalCache.java

@ -60,15 +60,20 @@ public interface TbTransactionalCache<K extends Serializable, V extends Serializ
}
default V getAndPutInTransaction(K key, Supplier<V> dbCall, boolean cacheNullValue) {
return getAndPutInTransaction(key, dbCall, Function.identity(), Function.identity(), cacheNullValue);
}
default <R> R getAndPutInTransaction(K key, Supplier<R> dbCall, Function<V, R> cacheValueToResult, Function<R, V> dbValueToCacheValue, boolean cacheNullValue) {
TbCacheValueWrapper<V> cacheValueWrapper = get(key);
if (cacheValueWrapper != null) {
return cacheValueWrapper.get();
V cacheValue = cacheValueWrapper.get();
return cacheValue != null ? cacheValueToResult.apply(cacheValue) : null;
}
var cacheTransaction = newTransactionForKey(key);
try {
V dbValue = dbCall.get();
R dbValue = dbCall.get();
if (dbValue != null || cacheNullValue) {
cacheTransaction.putIfAbsent(key, dbValue);
cacheTransaction.putIfAbsent(key, dbValueToCacheValue.apply(dbValue));
cacheTransaction.commit();
return dbValue;
} else {
@ -94,27 +99,4 @@ public interface TbTransactionalCache<K extends Serializable, V extends Serializ
}
}
default <R> R getAndPutInTransaction(K key, Supplier<R> dbCall, Function<V, R> cacheValueToResult, Function<R, V> dbValueToCacheValue, boolean cacheNullValue) {
TbCacheValueWrapper<V> cacheValueWrapper = get(key);
if (cacheValueWrapper != null) {
var cacheValue = cacheValueWrapper.get();
return cacheValue == null ? null : cacheValueToResult.apply(cacheValue);
}
var cacheTransaction = newTransactionForKey(key);
try {
R dbValue = dbCall.get();
if (dbValue != null || cacheNullValue) {
cacheTransaction.putIfAbsent(key, dbValueToCacheValue.apply(dbValue));
cacheTransaction.commit();
return dbValue;
} else {
cacheTransaction.rollback();
return null;
}
} catch (Throwable e) {
cacheTransaction.rollback();
throw e;
}
}
}

3
common/cache/src/test/java/org/thingsboard/server/cache/CacheSpecsMapTest.java

@ -62,4 +62,5 @@ public class CacheSpecsMapTest {
public void givenCacheConfig_whenCacheManagerReady_thenVerifyNonExistedCaches() {
assertThat(cacheManager.getCache("rainbows_and_unicorns")).isNull();
}
}
}

4
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmCreateOrUpdateActiveRequest.java

@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
@ -53,9 +54,12 @@ public class AlarmCreateOrUpdateActiveRequest implements AlarmModificationReques
private long startTs;
@Schema(description = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522")
private long endTs;
@ToString.Exclude
@NoXss
@Schema(description = "JSON object with alarm details")
private JsonNode details;
@Valid
@Schema(description = "JSON object with propagation details")
private AlarmPropagationInfo propagation;

4
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmUpdateRequest.java

@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
@ -47,9 +48,12 @@ public class AlarmUpdateRequest implements AlarmModificationRequest {
private long startTs;
@Schema(description = "Timestamp of the alarm end time(last time update), in milliseconds", example = "1634111163522")
private long endTs;
@ToString.Exclude
@NoXss
@Schema(description = "JSON object with alarm details")
private JsonNode details;
@Valid
@Schema(description = "JSON object with propagation details")
private AlarmPropagationInfo propagation;

516
common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java

@ -46,11 +46,23 @@ import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import static java.lang.Character.MAX_RADIX;
import static java.lang.Character.MIN_RADIX;
@Slf4j
public class TbUtils {
private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
private static final int ZERO_RADIX = 0;
private static final int OCTAL_RADIX = 8;
private static final int DEC_RADIX = 10;
private static final int HEX_RADIX = 16;
private static final int HEX_LEN_MIN = -1;
private static final int HEX_LEN_INT_MAX = 8;
private static final int HEX_LEN_LONG_MAX = 16;
private static final int BYTES_LEN_LONG_MAX = 8;
private static final LinkedHashMap<String, String> mdnEncodingReplacements = new LinkedHashMap<>();
static {
@ -103,6 +115,8 @@ public class TbUtils {
String.class, int.class)));
parserConfig.addImport("parseFloat", new MethodStub(TbUtils.class.getMethod("parseFloat",
String.class)));
parserConfig.addImport("parseFloat", new MethodStub(TbUtils.class.getMethod("parseFloat",
String.class, int.class)));
parserConfig.addImport("parseDouble", new MethodStub(TbUtils.class.getMethod("parseDouble",
String.class)));
parserConfig.addImport("parseLittleEndianHexToInt", new MethodStub(TbUtils.class.getMethod("parseLittleEndianHexToInt",
@ -175,6 +189,40 @@ public class TbUtils {
float.class, int.class)));
parserConfig.addImport("hexToBytes", new MethodStub(TbUtils.class.getMethod("hexToBytes",
ExecutionContext.class, String.class)));
parserConfig.addImport("intToHex", new MethodStub(TbUtils.class.getMethod("intToHex",
Integer.class)));
parserConfig.addImport("intToHex", new MethodStub(TbUtils.class.getMethod("intToHex",
Integer.class, boolean.class)));
parserConfig.addImport("intToHex", new MethodStub(TbUtils.class.getMethod("intToHex",
Integer.class, boolean.class, boolean.class)));
parserConfig.addImport("intToHex", new MethodStub(TbUtils.class.getMethod("intToHex",
Integer.class, boolean.class, boolean.class, int.class)));
parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex",
Long.class)));
parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex",
Long.class, boolean.class)));
parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex",
Long.class, boolean.class, boolean.class)));
parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex",
Long.class, boolean.class, boolean.class, int.class)));
parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString",
Long.class)));
parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString",
Long.class, int.class)));
parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString",
Long.class, int.class, boolean.class)));
parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString",
Long.class, int.class, boolean.class, boolean.class)));
parserConfig.addImport("floatToHex", new MethodStub(TbUtils.class.getMethod("floatToHex",
Float.class)));
parserConfig.addImport("floatToHex", new MethodStub(TbUtils.class.getMethod("floatToHex",
Float.class, boolean.class)));
parserConfig.addImport("doubleToHex", new MethodStub(TbUtils.class.getMethod("doubleToHex",
Double.class)));
parserConfig.addImport("doubleToHex", new MethodStub(TbUtils.class.getMethod("doubleToHex",
Double.class, boolean.class)));
parserConfig.addImport("printUnsignedBytes", new MethodStub(TbUtils.class.getMethod("printUnsignedBytes",
ExecutionContext.class, List.class)));
parserConfig.addImport("base64ToHex", new MethodStub(TbUtils.class.getMethod("base64ToHex",
String.class)));
parserConfig.addImport("base64ToBytes", new MethodStub(TbUtils.class.getMethod("base64ToBytes",
@ -197,6 +245,18 @@ public class TbUtils {
String.class)));
parserConfig.addImport("decodeURI", new MethodStub(TbUtils.class.getMethod("decodeURI",
String.class)));
parserConfig.addImport("raiseError", new MethodStub(TbUtils.class.getMethod("raiseError",
String.class, Object.class)));
parserConfig.addImport("raiseError", new MethodStub(TbUtils.class.getMethod("raiseError",
String.class)));
parserConfig.addImport("isBinary", new MethodStub(TbUtils.class.getMethod("isBinary",
String.class)));
parserConfig.addImport("isOctal", new MethodStub(TbUtils.class.getMethod("isOctal",
String.class)));
parserConfig.addImport("isDecimal", new MethodStub(TbUtils.class.getMethod("isDecimal",
String.class)));
parserConfig.addImport("isHexadecimal", new MethodStub(TbUtils.class.getMethod("isHexadecimal",
String.class)));
}
public static String btoa(String input) {
@ -210,6 +270,7 @@ public class TbUtils {
public static Object decodeToJson(ExecutionContext ctx, List<Byte> bytesList) throws IOException {
return TbJson.parse(ctx, bytesToString(bytesList));
}
public static Object decodeToJson(ExecutionContext ctx, String jsonStr) throws IOException {
return TbJson.parse(ctx, jsonStr);
}
@ -269,88 +330,127 @@ public class TbUtils {
}
public static Integer parseInt(String value) {
int radix = getRadix(value);
return parseInt(value, radix);
return parseInt(value, ZERO_RADIX);
}
public static Integer parseInt(String value, int radix) {
if (StringUtils.isNotBlank(value)) {
try {
String valueP = prepareNumberString(value);
isValidRadix(valueP, radix);
try {
return Integer.parseInt(valueP, radix);
} catch (NumberFormatException e) {
BigInteger bi = new BigInteger(valueP, radix);
if (bi.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0)
throw new NumberFormatException("Value \"" + value + "\" is greater than the maximum Integer value " + Integer.MAX_VALUE + " !");
if (bi.compareTo(BigInteger.valueOf(Integer.MIN_VALUE)) < 0)
throw new NumberFormatException("Value \"" + value + "\" is less than the minimum Integer value " + Integer.MIN_VALUE + " !");
Float f = parseFloat(valueP);
if (f != null) {
return f.intValue();
} else {
throw new NumberFormatException(e.getMessage());
}
}
} catch (NumberFormatException e) {
throw new NumberFormatException(e.getMessage());
String valueP = prepareNumberString(value);
int radixValue = isValidStringAndRadix(valueP, radix, value);
if (radixValue >= 25 && radixValue <= MAX_RADIX) {
return (Integer) compareIntLongValueMinMax(valueP, radixValue, Integer.MAX_VALUE, Integer.MIN_VALUE);
}
return switch (radixValue) {
case MIN_RADIX -> parseBinaryStringAsSignedInteger(valueP);
case OCTAL_RADIX, DEC_RADIX, HEX_RADIX -> Integer.parseInt(valueP, radixValue);
default -> throw new IllegalArgumentException("Invalid radix: [" + radix + "]");
};
}
return null;
}
public static Long parseLong(String value) {
int radix = getRadix(value);
return parseLong(value, radix);
return parseLong(value, ZERO_RADIX);
}
public static Long parseLong(String value, int radix) {
if (StringUtils.isNotBlank(value)) {
try {
String valueP = prepareNumberString(value);
isValidRadix(valueP, radix);
try {
return Long.parseLong(valueP, radix);
} catch (NumberFormatException e) {
BigInteger bi = new BigInteger(valueP, radix);
if (bi.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0)
throw new NumberFormatException("Value \"" + value + "\"is greater than the maximum Long value " + Long.MAX_VALUE + " !");
if (bi.compareTo(BigInteger.valueOf(Long.MIN_VALUE)) < 0)
throw new NumberFormatException("Value \"" + value + "\" is less than the minimum Long value " + Long.MIN_VALUE + " !");
Double dd = parseDouble(valueP);
if (dd != null) {
return dd.longValue();
} else {
throw new NumberFormatException(e.getMessage());
}
}
} catch (NumberFormatException e) {
throw new NumberFormatException(e.getMessage());
String valueP = prepareNumberString(value);
int radixValue = isValidStringAndRadix(valueP, radix, value);
if (radixValue >= 25 && radixValue <= MAX_RADIX) {
return (Long) compareIntLongValueMinMax(valueP, radixValue, Long.MAX_VALUE, Long.MIN_VALUE);
}
return switch (radixValue) {
case MIN_RADIX -> parseBinaryStringAsSignedLong(valueP);
case OCTAL_RADIX, DEC_RADIX, HEX_RADIX -> Long.parseLong(valueP, radixValue);
default -> throw new IllegalArgumentException("Invalid radix: [" + radix + "]");
};
}
return null;
}
private static int getRadix(String value, int... radixS) {
return radixS.length > 0 ? radixS[0] : isHexadecimal(value) ? 16 : 10;
private static int parseBinaryStringAsSignedInteger(String binaryString) {
if (binaryString.length() != 32) {
// Pad the binary string to 64 bits if it is not already
binaryString = String.format("%32s", binaryString).replace(' ', '0');
}
// If the MSB is 1, the number is negative in two's complement
if (binaryString.charAt(0) == '1') {
// Calculate the two's complement
String invertedBinaryString = invertBinaryString(binaryString);
int positiveValue = Integer.parseInt(invertedBinaryString, MIN_RADIX) + 1;
return -positiveValue;
} else {
return Integer.parseInt(binaryString, MIN_RADIX);
}
}
private static long parseBinaryStringAsSignedLong(String binaryString) {
if (binaryString.length() != 64) {
// Pad the binary string to 64 bits if it is not already
binaryString = String.format("%64s", binaryString).replace(' ', '0');
}
// If the MSB is 1, the number is negative in two's complement
if (binaryString.charAt(0) == '1') {
// Calculate the two's complement
String invertedBinaryString = invertBinaryString(binaryString);
long positiveValue = Long.parseLong(invertedBinaryString, MIN_RADIX) + 1;
return -positiveValue;
} else {
return Long.parseLong(binaryString, MIN_RADIX);
}
}
private static String invertBinaryString(String binaryString) {
StringBuilder invertedString = new StringBuilder();
for (char bit : binaryString.toCharArray()) {
invertedString.append(bit == '0' ? '1' : '0');
}
return invertedString.toString();
}
private static int getRadix10_16(String value) {
int radix = isDecimal(value) > 0 ? DEC_RADIX : isHexadecimal(value);
if (radix > 0) {
return radix;
} else {
throw new NumberFormatException("Value: \"" + value + "\" is not numeric or hexDecimal format!");
}
}
public static Float parseFloat(String value) {
if (value != null) {
try {
return Float.parseFloat(prepareNumberString(value));
} catch (NumberFormatException e) {
return parseFloat(value, 0);
}
public static Float parseFloat(String value, int radix) {
if (StringUtils.isNotBlank(value)) {
String valueP = prepareNumberString(value);
int radixValue = isValidStringAndRadix(valueP, radix, value);
if (radixValue == DEC_RADIX) {
return Float.parseFloat(value);
} else {
int bits = Integer.parseUnsignedInt(valueP, HEX_RADIX);
return Float.intBitsToFloat(bits);
}
}
return null;
}
public static Double parseDouble(String value) {
int radix = getRadix10_16(value);
return parseDouble(value, radix);
}
public static Double parseDouble(String value, int radix) {
if (value != null) {
try {
String valueP = prepareNumberString(value);
int radixValue = isValidStringAndRadix(valueP, radix, value);
if (radixValue == DEC_RADIX) {
return Double.parseDouble(prepareNumberString(value));
} catch (NumberFormatException e) {
} else {
long bits = Long.parseUnsignedLong(valueP, HEX_RADIX);
return Double.longBitsToDouble(bits);
}
}
return null;
@ -368,9 +468,10 @@ public class TbUtils {
return parseHexToInt(hex, true);
}
public static int parseHexToInt(String hex, boolean bigEndian) {
byte[] data = prepareHexToBytesNumber(hex, 8);
return parseBytesToInt(data, 0, data.length, bigEndian);
public static Integer parseHexToInt(String value, boolean bigEndian) {
String hexValue = prepareNumberString(value);
String hex = bigEndian ? hexValue : reverseHexStringByOrder(hexValue);
return parseInt(hex, HEX_RADIX);
}
public static long parseLittleEndianHexToLong(String hex) {
@ -385,9 +486,10 @@ public class TbUtils {
return parseHexToLong(hex, true);
}
public static long parseHexToLong(String hex, boolean bigEndian) {
byte[] data = prepareHexToBytesNumber(hex, 16);
return parseBytesToLong(data, 0, data.length, bigEndian);
public static Long parseHexToLong(String value, boolean bigEndian) {
String hexValue = prepareNumberString(value);
String hex = bigEndian ? value : reverseHexStringByOrder(hexValue);
return parseLong(hex, HEX_RADIX);
}
public static float parseLittleEndianHexToFloat(String hex) {
@ -402,9 +504,10 @@ public class TbUtils {
return parseHexToFloat(hex, true);
}
public static float parseHexToFloat(String hex, boolean bigEndian) {
byte[] data = prepareHexToBytesNumber(hex, 8);
return parseBytesToFloat(data, 0, bigEndian);
public static Float parseHexToFloat(String value, boolean bigEndian) {
String hexValue = prepareNumberString(value);
String hex = bigEndian ? value : reverseHexStringByOrder(hexValue);
return parseFloat(hex, HEX_RADIX);
}
public static double parseLittleEndianHexToDouble(String hex) {
@ -419,39 +522,171 @@ public class TbUtils {
return parseHexToDouble(hex, true);
}
public static double parseHexToDouble(String hex, boolean bigEndian) {
byte[] data = prepareHexToBytesNumber(hex, 16);
return parseBytesToDouble(data, 0, bigEndian);
public static double parseHexToDouble(String value, boolean bigEndian) {
String hexValue = prepareNumberString(value);
String hex = bigEndian ? value : reverseHexStringByOrder(hexValue);
return parseDouble(hex, HEX_RADIX);
}
private static byte[] prepareHexToBytesNumber(String hex, int len) {
int length = hex.length();
if (length > len) {
throw new IllegalArgumentException("Hex string is too large. Maximum 8 symbols allowed.");
}
if (length % 2 > 0) {
public static ExecutionArrayList<Byte> hexToBytes(ExecutionContext ctx, String value) {
String hex = prepareNumberString(value);
int len = hex.length();
if (len % 2 > 0) {
throw new IllegalArgumentException("Hex string must be even-length.");
}
byte[] data = new byte[length / 2];
for (int i = 0; i < length; i += 2) {
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + Character.digit(hex.charAt(i + 1), 16));
ExecutionArrayList<Byte> data = new ExecutionArrayList<>(ctx);
for (int i = 0; i < hex.length(); i += 2) {
// Extract two characters from the hex string
String byteString = hex.substring(i, i + 2);
// Parse the hex string to a byte
byte byteValue = (byte) Integer.parseInt(byteString, HEX_RADIX);
// Add the byte to the ArrayList
data.add(byteValue);
}
return data;
}
public static ExecutionArrayList<Byte> hexToBytes(ExecutionContext ctx, String hex) {
int len = hex.length();
if (len % 2 > 0) {
throw new IllegalArgumentException("Hex string must be even-length.");
}
ExecutionArrayList<Byte> data = new ExecutionArrayList<>(ctx);
for (int i = 0; i < len; i += 2) {
data.add((byte) ((Character.digit(hex.charAt(i), 16) << 4)
+ Character.digit(hex.charAt(i + 1), 16)));
public static List<Integer> printUnsignedBytes(ExecutionContext ctx, List<Byte> byteArray) {
ExecutionArrayList<Integer> data = new ExecutionArrayList<>(ctx);
for (Byte b : byteArray) {
// Convert signed byte to unsigned integer
int unsignedByte = Byte.toUnsignedInt(b);
data.add(unsignedByte);
}
return data;
}
public static String intToHex(Integer i) {
return prepareNumberHexString(i.longValue(), true, false, HEX_LEN_MIN, HEX_LEN_INT_MAX);
}
public static String intToHex(Integer i, boolean bigEndian) {
return prepareNumberHexString(i.longValue(), bigEndian, false, HEX_LEN_MIN, HEX_LEN_INT_MAX);
}
public static String intToHex(Integer i, boolean bigEndian, boolean pref) {
return prepareNumberHexString(i.longValue(), bigEndian, pref, HEX_LEN_MIN, HEX_LEN_INT_MAX);
}
public static String intToHex(Integer i, boolean bigEndian, boolean pref, int len) {
return prepareNumberHexString(i.longValue(), bigEndian, pref, len, HEX_LEN_INT_MAX);
}
public static String longToHex(Long l) {
return prepareNumberHexString(l, true, false, HEX_LEN_MIN, HEX_LEN_LONG_MAX);
}
public static String longToHex(Long l, boolean bigEndian) {
return prepareNumberHexString(l, bigEndian, false, HEX_LEN_MIN, HEX_LEN_LONG_MAX);
}
public static String longToHex(Long l, boolean bigEndian, boolean pref) {
return prepareNumberHexString(l, bigEndian, pref, HEX_LEN_MIN, HEX_LEN_LONG_MAX);
}
public static String longToHex(Long l, boolean bigEndian, boolean pref, int len) {
return prepareNumberHexString(l, bigEndian, pref, len, HEX_LEN_LONG_MAX);
}
public static String intLongToString(Long number) {
return intLongToString(number, DEC_RADIX);
}
public static String intLongToString(Long number, int radix) {
return intLongToString(number, radix, true);
}
public static String intLongToString(Long number, int radix, boolean bigEndian) {
return intLongToString(number, radix, bigEndian, false);
}
public static String intLongToString(Long number, int radix, boolean bigEndian, boolean pref) {
if (radix >= 25 && radix <= MAX_RADIX) {
return Long.toString(number, radix);
}
return switch (radix) {
case MIN_RADIX -> Long.toBinaryString(number);
case OCTAL_RADIX -> Long.toOctalString(number);
case DEC_RADIX -> Long.toString(number);
case HEX_RADIX -> prepareNumberHexString(number, bigEndian, pref, -1, -1);
default -> throw new IllegalArgumentException("Invalid radix: [" + radix + "]");
};
}
private static Number compareIntLongValueMinMax(String valueP, int radix, Number maxValue, Number minValue) {
boolean isInteger = maxValue.getClass().getSimpleName().equals("Integer");
try {
if (isInteger) {
return Integer.parseInt(valueP, radix);
} else {
return Long.parseLong(valueP, radix);
}
} catch (NumberFormatException e) {
BigInteger bi = new BigInteger(valueP, radix);
long maxValueL = isInteger ? maxValue.longValue() : (long) maxValue;
if (bi.compareTo(BigInteger.valueOf(maxValueL)) > 0) {
throw new NumberFormatException("Value \"" + valueP + "\"is greater than the maximum " + maxValue.getClass().getSimpleName() + " value " + maxValue + " !");
}
long minValueL = isInteger ? minValue.longValue() : (long) minValue;
if (bi.compareTo(BigInteger.valueOf(minValueL)) < 0) {
throw new NumberFormatException("Value \"" + valueP + "\" is less than the minimum " + minValue.getClass().getSimpleName() + " value " + minValue + " !");
}
throw new NumberFormatException(e.getMessage());
}
}
private static String prepareNumberHexString(Long number, boolean bigEndian, boolean pref, int len, int hexLenMax) {
String hex = Long.toHexString(number).toUpperCase();
hexLenMax = hexLenMax < 0 ? hex.length() : hexLenMax;
String hexWithoutZeroFF = removeLeadingZero_FF(hex, number, hexLenMax);
hexWithoutZeroFF = bigEndian ? hexWithoutZeroFF : reverseHexStringByOrder(hexWithoutZeroFF);
len = len == HEX_LEN_MIN ? hexWithoutZeroFF.length() : len;
String result = hexWithoutZeroFF.substring(hexWithoutZeroFF.length() - len);
return pref ? "0x" + result : result;
}
private static String removeLeadingZero_FF(String hex, Long number, int hexLenMax) {
String hexWithoutZero = hex.replaceFirst("^0+(?!$)", ""); // Remove leading zeros except for the last one
hexWithoutZero = hexWithoutZero.length() % 2 > 0 ? "0" + hexWithoutZero : hexWithoutZero;
if (number >= 0) {
return hexWithoutZero;
} else {
String hexWithoutZeroFF = hexWithoutZero.replaceFirst("^F+(?!$)", "");
hexWithoutZeroFF = hexWithoutZeroFF.length() % 2 > 0 ? "F" + hexWithoutZeroFF : hexWithoutZeroFF;
if (hexWithoutZeroFF.length() > hexLenMax) {
return hexWithoutZeroFF.substring(hexWithoutZeroFF.length() - hexLenMax);
} else if (hexWithoutZeroFF.length() == hexLenMax) {
return hexWithoutZeroFF;
} else {
return "FF" + hexWithoutZeroFF;
}
}
}
public static String floatToHex(Float f) {
return floatToHex(f, true);
}
public static String floatToHex(Float f, boolean bigEndian) {
// Convert the float to its raw integer bits representation
int bits = Float.floatToRawIntBits(f);
// Format the integer bits as a hexadecimal string
String result = String.format("0x%08X", bits);
return bigEndian ? result : reverseHexStringByOrder(result);
}
public static String doubleToHex(Double d) {
return doubleToHex(d, true);
}
public static String doubleToHex(Double d, boolean bigEndian) {
long bits = Double.doubleToRawLongBits(d);
// Format the integer bits as a hexadecimal string
String result = String.format("0x%16X", bits);
return bigEndian ? result : reverseHexStringByOrder(result);
}
public static String base64ToHex(String base64) {
return bytesToHex(Base64.getDecoder().decode(base64));
}
@ -520,8 +755,8 @@ public class TbUtils {
if (offset > data.length) {
throw new IllegalArgumentException("Offset: " + offset + " is out of bounds for array with length: " + data.length + "!");
}
if (length > 8) {
throw new IllegalArgumentException("Length: " + length + " is too large. Maximum 4 bytes is allowed!");
if (length > BYTES_LEN_LONG_MAX) {
throw new IllegalArgumentException("Length: " + length + " is too large. Maximum " + BYTES_LEN_LONG_MAX + " bytes is allowed!");
}
if (offset + length > data.length) {
throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!");
@ -567,7 +802,7 @@ public class TbUtils {
}
public static double parseBytesToDouble(byte[] data, int offset, boolean bigEndian) {
byte[] bytesToNumber = prepareBytesToNumber(data, offset, 8, bigEndian);
byte[] bytesToNumber = prepareBytesToNumber(data, offset, BYTES_LEN_LONG_MAX, bigEndian);
return ByteBuffer.wrap(bytesToNumber).getDouble();
}
@ -647,6 +882,15 @@ public class TbUtils {
return URLDecoder.decode(uri, StandardCharsets.UTF_8);
}
public static void raiseError(String message) {
raiseError(message, null);
}
public static void raiseError(String message, Object value) {
String msg = value == null ? message : message + " for value " + value;
throw new RuntimeException(msg);
}
private static void parseRecursive(Object json, Map<String, Object> map, List<String> excludeList, String path, boolean pathInKey) {
if (json instanceof Map.Entry) {
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) json;
@ -687,42 +931,104 @@ public class TbUtils {
}
}
private static boolean isHexadecimal(String value) {
return value != null && (value.contains("0x") || value.contains("0X"));
}
private static String prepareNumberString(String value) {
if (value != null) {
value = value.trim();
if (isHexadecimal(value)) {
value = value.replace("0x", "");
value = value.replace("0X", "");
}
value = value.replace("0x", "");
value = value.replace("0X", "");
value = value.replace(",", ".");
}
return value;
}
private static boolean isValidRadix(String value, int radix) {
for (int i = 0; i < value.length(); i++) {
if (i == 0 && value.charAt(i) == '-') {
if (value.length() == 1)
throw new NumberFormatException("Failed radix [" + radix + "] for value: \"" + value + "\"!");
else
continue;
private static int isValidStringAndRadix(String valueP, int radix, String value) {
int radixValue;
if (radix == 0) {
radixValue = getRadix10_16(valueP);
} else if (radix >= 25 && radix <= MAX_RADIX) {
return radix;
} else {
radixValue = switch (radix) {
case MIN_RADIX -> isBinary(valueP);
case OCTAL_RADIX -> isOctal(valueP);
case DEC_RADIX -> isDecimal(valueP);
case HEX_RADIX -> isHexadecimal(valueP);
default -> throw new IllegalArgumentException("Invalid radix: [" + radix + "]");
};
}
if (radixValue > 0) {
if (value.startsWith("0x")) radixValue = HEX_RADIX;
if (radixValue == HEX_RADIX) {
valueP = value.startsWith("-") ? value.substring(1) : value;
if (valueP.length() % 2 > 0) {
throw new NumberFormatException("The hexadecimal value: \"" + value + "\" must be of even length, or if the decimal value must be a number!");
}
}
return radixValue;
} else {
if (radix > 0) {
throw new NumberFormatException("Failed radix [" + radix + "] for value: \"" + value + "\", must be [" + radixValue + "] !");
} else {
throw new NumberFormatException("Invalid \"" + value + "\". It is not numeric or hexadecimal format!");
}
if (Character.digit(value.charAt(i), radix) < 0)
throw new NumberFormatException("Failed radix: [" + radix + "] for value: \"" + value + "\"!");
}
return true;
}
private static byte isValidIntegerToByte (Integer val) {
if (val > 255 || val.intValue() < -128) {
public static int isBinary(String str) {
if (str == null || str.isEmpty()) {
return -1;
}
return str.matches("[01]+") ? MIN_RADIX : -1;
}
public static int isOctal(String str) {
if (str == null || str.isEmpty()) {
return -1;
}
return str.matches("[0-7]+") ? OCTAL_RADIX : -1;
}
public static int isDecimal(String str) {
if (str == null || str.isEmpty()) {
return -1;
}
return str.matches("-?\\d+(\\.\\d+)?") ? DEC_RADIX : -1;
}
public static int isHexadecimal(String str) {
if (str == null || str.isEmpty()) {
return -1;
}
return str.matches("^-?(0[xX])?[0-9a-fA-F]+$") ? HEX_RADIX : -1;
}
private static byte isValidIntegerToByte(Integer val) {
if (val > 255 || val < -128) {
throw new NumberFormatException("The value '" + val + "' could not be correctly converted to a byte. " +
"Integer to byte conversion requires the use of only 8 bits (with a range of min/max = -128/255)!");
} else {
return val.byteValue();
}
}
private static String reverseHexStringByOrder(String value) {
if (value.startsWith("-")) {
throw new IllegalArgumentException("The hexadecimal string must be without a negative sign.");
}
boolean isHexPref = value.startsWith("0x");
String hex = isHexPref ? value.substring(2) : value;
if (hex.length() % 2 > 0) {
throw new IllegalArgumentException("The hexadecimal string must be even-length.");
}
// Split the hex string into bytes (2 characters each)
StringBuilder reversedHex = new StringBuilder(BYTES_LEN_LONG_MAX);
for (int i = hex.length() - 2; i >= 0; i -= 2) {
reversedHex.append(hex, i, i + 2);
}
String result = reversedHex.toString();
return isHexPref ? "0x" + result : result;
}
}

302
common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java

@ -15,12 +15,14 @@
*/
package org.thingsboard.script.api.tbel;
import com.google.common.collect.Lists;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.Ints;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mvel2.ExecutionContext;
import org.mvel2.ParserContext;
import org.mvel2.SandboxedParserConfiguration;
@ -31,28 +33,28 @@ import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import static java.lang.Character.MAX_RADIX;
import static java.lang.Character.MIN_RADIX;
@Slf4j
public class TbUtilsTest {
private ExecutionContext ctx;
private final String intValHex = "41EA62CC";
private final float floatVal = 29.29824f;
private final String floatValStr = "29.29824";
private final String floatValHexRev = "CC62EA41";
private final float floatValRev = -5.948442E7f;
private final long longVal = 0x409B04B10CB295EAL;
private final String longValHex = "409B04B10CB295EA";
private final long longValRev = 0xEA95B20CB1049B40L;
private final String longValHexRev = "EA95B20CB1049B40";
private final String doubleValStr = "1729.1729";
private final double doubleVal = 1729.1729;
private final double doubleValRev = -2.7208640774822924E205;
@ -86,10 +88,9 @@ public class TbUtilsTest {
Assertions.assertEquals(0xBAAB, TbUtils.parseHexToInt("ABBA", false));
Assertions.assertEquals(0xAABBCC, TbUtils.parseHexToInt("AABBCC", true));
Assertions.assertEquals(0xAABBCC, TbUtils.parseHexToInt("CCBBAA", false));
Assertions.assertEquals(0xAABBCCDD, TbUtils.parseHexToInt("AABBCCDD", true));
Assertions.assertEquals(0xAABBCCDD, TbUtils.parseHexToInt("DDCCBBAA", false));
Assertions.assertEquals(0xDDCCBBAA, TbUtils.parseHexToInt("DDCCBBAA", true));
Assertions.assertEquals(0xDDCCBBAA, TbUtils.parseHexToInt("AABBCCDD", false));
Assertions.assertThrows(NumberFormatException.class, () -> TbUtils.parseHexToInt("AABBCCDD", true));
Assertions.assertEquals(0x11BBCC22, TbUtils.parseHexToInt("11BBCC22", true));
Assertions.assertEquals(0x11BBCC22, TbUtils.parseHexToInt("22CCBB11", false));
}
@Test
@ -208,15 +209,28 @@ public class TbUtilsTest {
Assertions.assertNull(TbUtils.parseInt(""));
Assertions.assertNull(TbUtils.parseInt(" "));
Assertions.assertEquals(java.util.Optional.of(0).get(), TbUtils.parseInt("0"));
Assertions.assertEquals(java.util.Optional.of(0).get(), TbUtils.parseInt("-0"));
Assertions.assertEquals((Integer) 0, TbUtils.parseInt("0"));
Assertions.assertEquals((Integer) 0, TbUtils.parseInt("-0"));
Assertions.assertEquals(java.util.Optional.of(473).get(), TbUtils.parseInt("473"));
Assertions.assertEquals(java.util.Optional.of(-255).get(), TbUtils.parseInt("-0xFF"));
Assertions.assertThrows(NumberFormatException.class, () -> TbUtils.parseInt("FF"));
Assertions.assertThrows(NumberFormatException.class, () -> TbUtils.parseInt("-0xFF123"));
Assertions.assertEquals(java.util.Optional.of(-255).get(), TbUtils.parseInt("-FF"));
Assertions.assertEquals(java.util.Optional.of(255).get(), TbUtils.parseInt("FF"));
Assertions.assertThrows(IllegalArgumentException.class, () -> TbUtils.parseInt("FFF"));
Assertions.assertEquals(java.util.Optional.of(-2578).get(), TbUtils.parseInt("-0A12"));
Assertions.assertEquals(java.util.Optional.of(-2578).get(), TbUtils.parseHexToInt("-0A12"));
Assertions.assertThrows(IllegalArgumentException.class, () -> TbUtils.parseHexToInt("A12", false));
Assertions.assertEquals(java.util.Optional.of(-14866).get(), TbUtils.parseBigEndianHexToInt("-3A12"));
Assertions.assertThrows(IllegalArgumentException.class, () -> TbUtils.parseLittleEndianHexToInt("-A12"));
Assertions.assertThrows(NumberFormatException.class, () -> TbUtils.parseInt("0xFG"));
Assertions.assertEquals(java.util.Optional.of(102).get(), TbUtils.parseInt("1100110", 2));
Assertions.assertEquals(java.util.Optional.of(-102).get(), TbUtils.parseInt("1111111111111111111111111111111111111111111111111111111110011010", 2));
Assertions.assertThrows(NumberFormatException.class, () -> TbUtils.parseInt("1100210", 2));
Assertions.assertEquals(java.util.Optional.of(13158).get(), TbUtils.parseInt("11001101100110", 2));
Assertions.assertEquals(java.util.Optional.of(-13158).get(), TbUtils.parseInt("1111111111111111111111111111111111111111111111111100110010011010", 2));
Assertions.assertEquals(java.util.Optional.of(63).get(), TbUtils.parseInt("77", 8));
Assertions.assertThrows(NumberFormatException.class, () -> TbUtils.parseInt("18", 8));
@ -224,19 +238,32 @@ public class TbUtilsTest {
Assertions.assertEquals(java.util.Optional.of(-255).get(), TbUtils.parseInt("-FF", 16));
Assertions.assertThrows(NumberFormatException.class, () -> TbUtils.parseInt("FG", 16));
Assertions.assertEquals(java.util.Optional.of(Integer.MAX_VALUE).get(), TbUtils.parseInt(Integer.toString(Integer.MAX_VALUE), 10));
Assertions.assertThrows(NumberFormatException.class, () -> TbUtils.parseInt(BigInteger.valueOf(Integer.MAX_VALUE).add(BigInteger.valueOf(1)).toString(10), 10));
Assertions.assertEquals(java.util.Optional.of(Integer.MIN_VALUE).get(), TbUtils.parseInt(Integer.toString(Integer.MIN_VALUE), 10));
Assertions.assertThrows(NumberFormatException.class, () -> TbUtils.parseInt(BigInteger.valueOf(Integer.MIN_VALUE).subtract(BigInteger.valueOf(1)).toString(10), 10));
Assertions.assertEquals(java.util.Optional.of(506070563).get(), TbUtils.parseInt("KonaIn", 30));
Assertions.assertThrows(NumberFormatException.class, () -> TbUtils.parseInt("KonaIn", 10));
Assertions.assertThrows(NumberFormatException.class, () -> TbUtils.parseInt(".456", 10));
Assertions.assertThrows(NumberFormatException.class, () -> TbUtils.parseInt("4562.", 10));
Assertions.assertThrows(IllegalArgumentException.class, () -> TbUtils.parseInt("KonaIn", MAX_RADIX + 1));
Assertions.assertThrows(IllegalArgumentException.class, () -> TbUtils.parseInt("KonaIn", MIN_RADIX - 1));
Assertions.assertThrows(IllegalArgumentException.class, () -> TbUtils.parseInt("KonaIn", 12));
}
@Test
public void parseFloat() {
String floatValStr = "29.29824";
Assertions.assertEquals(java.util.Optional.of(floatVal).get(), TbUtils.parseFloat(floatValStr));
String floatValHex = "41EA62CC";
Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseHexToFloat(floatValHex)));
Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseHexToFloat(floatValHex, false)));
Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBigEndianHexToFloat(floatValHex)));
String floatValHexRev = "CC62EA41";
Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseLittleEndianHexToFloat(floatValHexRev)));
}
@Test
@ -246,21 +273,13 @@ public class TbUtilsTest {
Assertions.assertEquals(0, Float.compare(29.298f, actualF));
}
@Test
public void parseHexToFloat() {
Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseHexToFloat(intValHex)));
Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseHexToFloat(intValHex, false)));
Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBigEndianHexToFloat(intValHex)));
Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseLittleEndianHexToFloat(floatValHexRev)));
}
@Test
public void arseBytesToFloat() {
byte[] floatValByte = {65, -22, 98, -52};
Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBytesToFloat(floatValByte, 0)));
Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatValByte, 0, false)));
List <Byte> floatVaList = Bytes.asList(floatValByte);
List<Byte> floatVaList = Bytes.asList(floatValByte);
Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBytesToFloat(floatVaList, 0)));
Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatVaList, 0, false)));
}
@ -271,14 +290,15 @@ public class TbUtilsTest {
Assertions.assertNull(TbUtils.parseLong(""));
Assertions.assertNull(TbUtils.parseLong(" "));
Assertions.assertEquals(java.util.Optional.of(0L).get(), TbUtils.parseLong("0"));
Assertions.assertEquals(java.util.Optional.of(0L).get(), TbUtils.parseLong("-0"));
Assertions.assertEquals((Long) 0L, TbUtils.parseLong("0"));
Assertions.assertEquals((Long) 0L, TbUtils.parseLong("-0"));
Assertions.assertEquals(java.util.Optional.of(473L).get(), TbUtils.parseLong("473"));
Assertions.assertEquals(java.util.Optional.of(-65535L).get(), TbUtils.parseLong("-0xFFFF"));
Assertions.assertThrows(NumberFormatException.class, () -> TbUtils.parseLong("FFFFFFFF"));
Assertions.assertEquals(java.util.Optional.of(4294967295L).get(), TbUtils.parseLong("FFFFFFFF"));
Assertions.assertThrows(NumberFormatException.class, () -> TbUtils.parseLong("0xFGFFFFFF"));
Assertions.assertEquals(java.util.Optional.of(13158L).get(), TbUtils.parseLong("11001101100110", 2));
Assertions.assertEquals(java.util.Optional.of(-13158L).get(), TbUtils.parseLong("1111111111111111111111111111111111111111111111111100110010011010", 2));
Assertions.assertThrows(NumberFormatException.class, () -> TbUtils.parseLong("11001101100210", 2));
Assertions.assertEquals(java.util.Optional.of(9223372036854775807L).get(), TbUtils.parseLong("777777777777777777777", 8));
@ -295,10 +315,7 @@ public class TbUtilsTest {
Assertions.assertEquals(java.util.Optional.of(218840926543L).get(), TbUtils.parseLong("KonaLong", 27));
Assertions.assertThrows(NumberFormatException.class, () -> TbUtils.parseLong("KonaLong", 10));
}
@Test
public void parseHexToLong() {
Assertions.assertEquals(longVal, TbUtils.parseHexToLong(longValHex));
Assertions.assertEquals(longVal, TbUtils.parseHexToLong(longValHexRev, false));
Assertions.assertEquals(longVal, TbUtils.parseBigEndianHexToLong(longValHex));
@ -312,14 +329,20 @@ public class TbUtilsTest {
Bytes.reverse(longValByte);
Assertions.assertEquals(longVal, TbUtils.parseBytesToLong(longValByte, 0, 8, false));
List <Byte> longVaList = Bytes.asList(longValByte);
List<Byte> longVaList = Bytes.asList(longValByte);
Assertions.assertEquals(longVal, TbUtils.parseBytesToLong(longVaList, 0, 8, false));
long longValRev = 0xEA95B20CB1049B40L;
Assertions.assertEquals(longValRev, TbUtils.parseBytesToLong(longVaList, 0, 8));
}
@Test
public void parsDouble() {
String doubleValStr = "1729.1729";
Assertions.assertEquals(java.util.Optional.of(doubleVal).get(), TbUtils.parseDouble(doubleValStr));
Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseHexToDouble(longValHex)));
Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseHexToDouble(longValHex, false)));
Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseBigEndianHexToDouble(longValHex)));
Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseLittleEndianHexToDouble(longValHexRev)));
}
@Test
@ -329,21 +352,13 @@ public class TbUtilsTest {
Assertions.assertEquals(0, Double.compare(1729.173, actualD));
}
@Test
public void parseHexToDouble() {
Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseHexToDouble(longValHex)));
Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseHexToDouble(longValHex, false)));
Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseBigEndianHexToDouble(longValHex)));
Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseLittleEndianHexToDouble(longValHexRev)));
}
@Test
public void parseBytesToDouble() {
byte[] doubleValByte = {64, -101, 4, -79, 12, -78, -107, -22};
Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseBytesToDouble(doubleValByte, 0)));
Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleValByte, 0, false)));
List <Byte> doubleVaList = Bytes.asList(doubleValByte);
List<Byte> doubleVaList = Bytes.asList(doubleValByte);
Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseBytesToDouble(doubleVaList, 0)));
Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleVaList, 0, false)));
}
@ -354,16 +369,17 @@ public class TbUtilsTest {
ExecutionHashMap<String, Object> expectedJson = new ExecutionHashMap<>(1, ctx);
expectedJson.put("hello", "world");
List<Byte> expectedBytes = TbUtils.stringToBytes(ctx, expectedStr);
Object actualJson = TbUtils.decodeToJson(ctx, expectedBytes);
Assertions.assertEquals(expectedJson,actualJson);
Object actualJson = TbUtils.decodeToJson(ctx, expectedBytes);
Assertions.assertEquals(expectedJson, actualJson);
}
@Test
public void parseStringDecodeToJson() throws IOException {
String expectedStr = "{\"hello\": \"world\"}";
ExecutionHashMap<String, Object> expectedJson = new ExecutionHashMap<>(1, ctx);
expectedJson.put("hello", "world");
Object actualJson = TbUtils.decodeToJson(ctx, expectedStr);
Assertions.assertEquals(expectedJson,actualJson);
Object actualJson = TbUtils.decodeToJson(ctx, expectedStr);
Assertions.assertEquals(expectedJson, actualJson);
}
@Test
@ -386,8 +402,8 @@ public class TbUtilsTest {
@Test
public void bytesFromList() {
byte[] arrayBytes = {(byte)0x00, (byte)0x08, (byte)0x10, (byte)0x1C, (byte)0xFF, (byte)0xFC, (byte)0xAD, (byte)0x88, (byte)0x75, (byte)0x74, (byte)0x8A, (byte)0x82};
Object[] arrayMix = { "0x00", 8, "16", "0x1C", 255, (byte)0xFC, 173, 136, 117, 116, -118, "-126"};
byte[] arrayBytes = {(byte) 0x00, (byte) 0x08, (byte) 0x10, (byte) 0x1C, (byte) 0xFF, (byte) 0xFC, (byte) 0xAD, (byte) 0x88, (byte) 0x75, (byte) 0x74, (byte) 0x8A, (byte) 0x82};
Object[] arrayMix = {"0x00", 8, "16", "0x1C", 255, (byte) 0xFC, 173, 136, 117, 116, -118, "-126"};
String expected = new String(arrayBytes);
ArrayList<Byte> listBytes = new ArrayList<>(arrayBytes.length);
@ -397,12 +413,36 @@ public class TbUtilsTest {
Assertions.assertEquals(expected, TbUtils.bytesToString(listBytes));
ArrayList<Object> listMix = new ArrayList<>(arrayMix.length);
for (Object element : arrayMix) {
listMix.add(element);
}
Collections.addAll(listMix, arrayMix);
Assertions.assertEquals(expected, TbUtils.bytesToString(listMix));
}
@Test
public void bytesFromList_SpecSymbol() {
List<String> listHex = new ArrayList<>(Arrays.asList("1D", "0x1D", "1F", "0x1F", "0x20", "0x20"));
byte[] expectedBytes = new byte[]{29, 29, 31, 31, 32, 32};
String actualStr = TbUtils.bytesToString(listHex);
byte[] actualBytes = actualStr.getBytes();
Assertions.assertArrayEquals(expectedBytes, actualBytes);
Assertions.assertTrue(actualStr.isBlank());
listHex = new ArrayList<>(Arrays.asList("0x21", "0x21"));
expectedBytes = new byte[]{33, 33};
actualStr = TbUtils.bytesToString(listHex);
actualBytes = actualStr.getBytes();
Assertions.assertArrayEquals(expectedBytes, actualBytes);
Assertions.assertFalse(actualStr.isBlank());
Assertions.assertEquals("!!", actualStr);
listHex = new ArrayList<>(Arrays.asList("21", "0x21"));
expectedBytes = new byte[]{21, 33};
actualStr = TbUtils.bytesToString(listHex);
actualBytes = actualStr.getBytes();
Assertions.assertArrayEquals(expectedBytes, actualBytes);
Assertions.assertFalse(actualStr.isBlank());
Assertions.assertEquals("!", actualStr.substring(1));
Assertions.assertEquals('\u0015', actualStr.charAt(0));
Assertions.assertEquals(21, actualStr.charAt(0));
}
@Test
public void bytesFromList_Error() {
List<String> listHex = new ArrayList<>();
@ -411,14 +451,7 @@ public class TbUtilsTest {
TbUtils.bytesToString(listHex);
Assertions.fail("Should throw NumberFormatException");
} catch (NumberFormatException e) {
Assertions.assertTrue(e.getMessage().contains("Failed radix: [16] for value: \"FG\"!"));
}
listHex.add(0, "1F");
try {
TbUtils.bytesToString(listHex);
Assertions.fail("Should throw NumberFormatException");
} catch (NumberFormatException e) {
Assertions.assertTrue(e.getMessage().contains("Failed radix: [10] for value: \"1F\"!"));
Assertions.assertTrue(e.getMessage().contains("Value: \"FG\" is not numeric or hexDecimal format!"));
}
List<String> listIntString = new ArrayList<>();
@ -482,6 +515,161 @@ public class TbUtilsTest {
String uriDecodeActual = TbUtils.decodeURI(uriEncodeActual);
Assertions.assertEquals(uriOriginal, uriDecodeActual);
}
@Test
public void intToHex_Test() {
Assertions.assertEquals("FFF5EE", TbUtils.intToHex(-2578));
Assertions.assertEquals("0xFFD8FFA6", TbUtils.intToHex(0xFFD8FFA6, true, true));
Assertions.assertEquals("0xA6FFD8FF", TbUtils.intToHex(0xFFD8FFA6, false, true));
Assertions.assertEquals("0x7FFFFFFF", TbUtils.intToHex(Integer.MAX_VALUE, true, true));
Assertions.assertEquals("0x80000000", TbUtils.intToHex(Integer.MIN_VALUE, true, true));
Assertions.assertEquals("0xAB", TbUtils.intToHex(0xAB, true, true));
Assertions.assertEquals("0xABCD", TbUtils.intToHex(0xABCD, true, true));
Assertions.assertEquals("0xABCDEF", TbUtils.intToHex(0xABCDEF, true, true));
Assertions.assertEquals("0xCDAB", TbUtils.intToHex(0xABCDEF, false, true, 4));
Assertions.assertEquals("0xAB", TbUtils.intToHex(171, true, true));
Assertions.assertEquals("0xAB", TbUtils.intToHex(0xAB, false, true));
Assertions.assertEquals("0xAB", TbUtils.intToHex(0xAB, true, true, 2));
Assertions.assertEquals("AB", TbUtils.intToHex(0xAB, false, false, 2));
Assertions.assertEquals("AB", TbUtils.intToHex(171, true, false));
Assertions.assertEquals("0xAB", TbUtils.intToHex(0xAB, true, true));
Assertions.assertEquals("0xAB", TbUtils.intToHex(0xAB, false, true));
Assertions.assertEquals("AB", TbUtils.intToHex(0xAB, false, false));
Assertions.assertEquals("0xABCD", TbUtils.intToHex(0xABCD, true, true));
Assertions.assertEquals("0xCDAB", TbUtils.intToHex(0xABCD, false, true));
Assertions.assertEquals("0xCD", TbUtils.intToHex(0xABCD, true, true, 2));
Assertions.assertEquals("AB", TbUtils.intToHex(0xABCD, false, false, 2));
}
@Test
public void longToHex_Test() {
Assertions.assertEquals("0x7FFFFFFFFFFFFFFF", TbUtils.longToHex(Long.MAX_VALUE, true, true));
Assertions.assertEquals("0x8000000000000000", TbUtils.longToHex(Long.MIN_VALUE, true, true));
Assertions.assertEquals("0xFFD8FFA6FFD8FFA6", TbUtils.longToHex(0xFFD8FFA6FFD8FFA6L, true, true));
Assertions.assertEquals("0xA6FFD8FFA6FFCEFF", TbUtils.longToHex(0xFFCEFFA6FFD8FFA6L, false, true));
Assertions.assertEquals("0xAB", TbUtils.longToHex(0xABL, true, true));
Assertions.assertEquals("0xABCD", TbUtils.longToHex(0xABCDL, true, true));
Assertions.assertEquals("0xABCDEF", TbUtils.longToHex(0xABCDEFL, true, true));
Assertions.assertEquals("0xABEFCDAB", TbUtils.longToHex(0xABCDEFABCDEFL, false, true, 8));
Assertions.assertEquals("0xAB", TbUtils.longToHex(0xABL, true, true, 2));
Assertions.assertEquals("AB", TbUtils.longToHex(0xABL, false, false, 2));
Assertions.assertEquals("0xFFA6", TbUtils.longToHex(0xFFD8FFA6FFD8FFA6L, true, true, 4));
Assertions.assertEquals("D8FF", TbUtils.longToHex(0xFFD8FFA6FFD8FFA6L, false, false, 4));
}
@Test
public void numberToString_Test() {
Assertions.assertEquals("11001101100110", TbUtils.intLongToString(13158L, 2));
Assertions.assertEquals("1111111111111111111111111111111111111111111111111111111110011010", TbUtils.intLongToString(-102L, 2));
Assertions.assertEquals("1111111111111111111111111111111111111111111111111100110010011010", TbUtils.intLongToString(-13158L, 2));
Assertions.assertEquals("777777777777777777777", TbUtils.intLongToString(Long.MAX_VALUE, 8));
Assertions.assertEquals("1000000000000000000000", TbUtils.intLongToString(Long.MIN_VALUE, 8));
Assertions.assertEquals("9223372036854775807", TbUtils.intLongToString(Long.MAX_VALUE));
Assertions.assertEquals("-9223372036854775808", TbUtils.intLongToString(Long.MIN_VALUE));
Assertions.assertEquals("3366", TbUtils.intLongToString(13158L, 16));
Assertions.assertEquals("FFCC9A", TbUtils.intLongToString(-13158L, 16));
Assertions.assertEquals("0xFFCC9A", TbUtils.intLongToString(-13158L, 16, true, true));
Assertions.assertEquals("0x0400", TbUtils.intLongToString(1024L, 16, true, true));
Assertions.assertNotEquals("400", TbUtils.intLongToString(1024L, 16));
Assertions.assertEquals("0xFFFC00", TbUtils.intLongToString(-1024L, 16, true, true));
Assertions.assertNotEquals("0xFC00", TbUtils.intLongToString(-1024L, 16, true, true));
Assertions.assertEquals("hazelnut", TbUtils.intLongToString(1356099454469L, MAX_RADIX));
}
@Test
public void intToHexWithPrintUnsignedBytes_Test() {
Integer value = -40;
String intToHexLe = TbUtils.intToHex(value, false, true);
String intToHexBe = TbUtils.intToHex(value, true, true);
List<Byte> hexTopByteLe = TbUtils.hexToBytes(ctx, intToHexLe);
List<Byte> hexTopByteBe = TbUtils.hexToBytes(ctx, intToHexBe);
byte[] arrayBytes = {-40, -1};
List<Byte> expectedHexTopByteLe = Bytes.asList(arrayBytes);
List<Byte> expectedHexTopByteBe = Lists.reverse(expectedHexTopByteLe);
Assertions.assertEquals(expectedHexTopByteLe, hexTopByteLe);
Assertions.assertEquals(expectedHexTopByteBe, hexTopByteBe);
List<Integer> actualLe = TbUtils.printUnsignedBytes(ctx, hexTopByteLe);
List<Integer> actualBe = TbUtils.printUnsignedBytes(ctx, hexTopByteBe);
List<Integer> expectedLe = Ints.asList(216, 255);
List<Integer> expectedBe = Lists.reverse(expectedLe);
Assertions.assertEquals(expectedLe, actualLe);
Assertions.assertEquals(expectedBe, actualBe);
}
@Test
public void floatToHex_Test() {
Float value = 20.89f;
String expectedHex = "0x41A71EB8";
String valueHexRev = "0xB81EA741";
String actual = TbUtils.floatToHex(value);
Assertions.assertEquals(expectedHex, actual);
Float valueActual = TbUtils.parseHexToFloat(actual);
Assertions.assertEquals(value, valueActual);
valueActual = TbUtils.parseHexToFloat(valueHexRev, false);
Assertions.assertEquals(value, valueActual);
}
@Test
public void doubleToHex_Test() {
String expectedHex = "0x409B04B10CB295EA";
String actual = TbUtils.doubleToHex(doubleVal);
Assertions.assertEquals(expectedHex, actual);
Double valueActual = TbUtils.parseHexToDouble(actual);
Assertions.assertEquals(doubleVal, valueActual);
actual = TbUtils.doubleToHex(doubleVal, false);
String expectedHexRev = "0xEA95B20CB1049B40";
Assertions.assertEquals(expectedHexRev, actual);
}
@Test
public void raiseError_Test() {
String message = "frequency_weighting_type must be 0, 1 or 2.";
Object value = 4;
try {
TbUtils.raiseError(message, value);
Assertions.fail("Should throw NumberFormatException");
} catch (RuntimeException e) {
Assertions.assertTrue(e.getMessage().contains("frequency_weighting_type must be 0, 1 or 2. for value 4"));
}
try {
TbUtils.raiseError(message);
Assertions.fail("Should throw NumberFormatException");
} catch (RuntimeException e) {
Assertions.assertTrue(e.getMessage().contains("frequency_weighting_type must be 0, 1 or 2."));
}
}
@Test
public void isBinary_Test() {
Assertions.assertEquals(2, TbUtils.isBinary("1100110"));
Assertions.assertEquals(-1, TbUtils.isBinary("2100110"));
}
@Test
public void isOctal_Test() {
Assertions.assertEquals(8, TbUtils.isOctal("4567734"));
Assertions.assertEquals(-1, TbUtils.isOctal("8100110"));
}
@Test
public void isDecimal_Test() {
Assertions.assertEquals(10, TbUtils.isDecimal("4567039"));
Assertions.assertEquals(-1, TbUtils.isDecimal("C100110"));
}
@Test
public void isHexadecimal_Test() {
Assertions.assertEquals(16, TbUtils.isHexadecimal("F5D7039"));
Assertions.assertEquals(-1, TbUtils.isHexadecimal("K100110"));
}
private static List<Byte> toList(byte[] data) {
List<Byte> result = new ArrayList<>(data.length);
for (Byte b : data) {

15
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java

@ -342,9 +342,12 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
@Override
public AlarmApiCallResult createOrUpdateActiveAlarm(AlarmCreateOrUpdateActiveRequest request, boolean alarmCreationEnabled) {
UUID tenantUUID = request.getTenantId().getId();
log.debug("[{}] createOrUpdateActiveAlarm [{}] {}", tenantUUID, alarmCreationEnabled, request);
AlarmPropagationInfo ap = getSafePropagationInfo(request.getPropagation());
return toAlarmApiResult(alarmRepository.createOrUpdateActiveAlarm(
request.getTenantId().getId(),
tenantUUID,
request.getCustomerId() != null ? request.getCustomerId().getId() : CustomerId.NULL_UUID,
request.getEdgeAlarmId() != null ? request.getEdgeAlarmId().getId() : UUID.randomUUID(),
System.currentTimeMillis(),
@ -364,10 +367,14 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
@Override
public AlarmApiCallResult updateAlarm(AlarmUpdateRequest request) {
UUID tenantUUID = request.getTenantId().getId();
UUID alarmUUID = request.getAlarmId().getId();
log.debug("[{}][{}] updateAlarm {}", tenantUUID, alarmUUID, request);
AlarmPropagationInfo ap = getSafePropagationInfo(request.getPropagation());
return toAlarmApiResult(alarmRepository.updateAlarm(
request.getTenantId().getId(),
request.getAlarmId().getId(),
tenantUUID,
alarmUUID,
request.getSeverity().name(),
request.getStartTs(), request.getEndTs(),
getDetailsAsString(request.getDetails()),
@ -380,11 +387,13 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
@Override
public AlarmApiCallResult acknowledgeAlarm(TenantId tenantId, AlarmId id, long ackTs) {
log.debug("[{}][{}] acknowledgeAlarm [{}]", tenantId, id, ackTs);
return toAlarmApiResult(alarmRepository.acknowledgeAlarm(tenantId.getId(), id.getId(), ackTs));
}
@Override
public AlarmApiCallResult clearAlarm(TenantId tenantId, AlarmId id, long clearTs, JsonNode details) {
log.debug("[{}][{}] clearAlarm [{}]", tenantId, id, clearTs);
return toAlarmApiResult(alarmRepository.clearAlarm(tenantId.getId(), id.getId(), clearTs, details != null ? getDetailsAsString(details) : null));
}

82
dao/src/test/java/org/thingsboard/server/dao/cache/RedisTbTransactionalCacheTest.java

@ -0,0 +1,82 @@
/**
* Copyright © 2016-2024 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.dao.cache;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.thingsboard.server.cache.CacheSpecsMap;
import org.thingsboard.server.cache.RedisSslCredentials;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.relation.RelationCacheKey;
import org.thingsboard.server.dao.relation.RelationRedisCache;
import java.util.List;
import java.util.UUID;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {RelationRedisCache.class, CacheSpecsMap.class, TBRedisCacheConfiguration.class})
@TestPropertySource(properties = {
"cache.type=redis",
"cache.specs.relations.timeToLiveInMinutes=1440",
"cache.specs.relations.maxSize=0",
})
@Slf4j
public class RedisTbTransactionalCacheTest {
@MockBean
private RelationRedisCache relationRedisCache;
@MockBean
private RedisConnectionFactory connectionFactory;
@MockBean
private RedisConnection redisConnection;
@MockBean
private RedisSslCredentials redisSslCredentials;
@Test
public void testNoOpWhenCacheDisabled() {
when(connectionFactory.getConnection()).thenReturn(redisConnection);
relationRedisCache.put(createRelationCacheKey(), null);
relationRedisCache.putIfAbsent(createRelationCacheKey(), null);
relationRedisCache.evict(createRelationCacheKey());
relationRedisCache.evict(List.of(createRelationCacheKey()));
relationRedisCache.getAndPutInTransaction(createRelationCacheKey(), null, false);
relationRedisCache.getAndPutInTransaction(createRelationCacheKey(), null, null, null, false);
relationRedisCache.getOrFetchFromDB(createRelationCacheKey(), null, false, false);
verify(connectionFactory, never()).getConnection();
verifyNoInteractions(redisConnection);
}
private RelationCacheKey createRelationCacheKey() {
return new RelationCacheKey(new DeviceId(UUID.randomUUID()), new DeviceId(UUID.randomUUID()), null, RelationTypeGroup.COMMON);
}
}

87
dao/src/test/java/org/thingsboard/server/dao/service/AlarmServiceTest.java

@ -58,7 +58,6 @@ import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.user.UserService;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
@ -245,8 +244,8 @@ public class AlarmServiceTest extends AbstractServiceTest {
// Check child relation
PageData<AlarmInfo> alarms = alarmService.findAlarmsV2(tenantId, AlarmQueryV2.builder()
.affectedEntityId(childId)
.severityList(Arrays.asList(AlarmSeverity.CRITICAL))
.statusList(Arrays.asList(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink(
.severityList(List.of(AlarmSeverity.CRITICAL))
.statusList(List.of(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink(
new TimePageLink(1, 0, "",
new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis())
).build());
@ -257,8 +256,8 @@ public class AlarmServiceTest extends AbstractServiceTest {
// Check parent relation
alarms = alarmService.findAlarmsV2(tenantId, AlarmQueryV2.builder()
.affectedEntityId(parentId)
.severityList(Arrays.asList(AlarmSeverity.CRITICAL))
.statusList(Arrays.asList(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink(
.severityList(List.of(AlarmSeverity.CRITICAL))
.statusList(List.of(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink(
new TimePageLink(1, 0, "",
new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis())
).build());
@ -272,8 +271,8 @@ public class AlarmServiceTest extends AbstractServiceTest {
// Check child relation
alarms = alarmService.findAlarmsV2(tenantId, AlarmQueryV2.builder()
.affectedEntityId(childId)
.severityList(Arrays.asList(AlarmSeverity.CRITICAL))
.statusList(Arrays.asList(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink(
.severityList(List.of(AlarmSeverity.CRITICAL))
.statusList(List.of(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink(
new TimePageLink(1, 0, "",
new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis())
).build());
@ -284,8 +283,8 @@ public class AlarmServiceTest extends AbstractServiceTest {
// Check parent relation
alarms = alarmService.findAlarmsV2(tenantId, AlarmQueryV2.builder()
.affectedEntityId(parentId)
.severityList(Arrays.asList(AlarmSeverity.CRITICAL))
.statusList(Arrays.asList(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink(
.severityList(List.of(AlarmSeverity.CRITICAL))
.statusList(List.of(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink(
new TimePageLink(1, 0, "",
new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis())
).build());
@ -298,8 +297,8 @@ public class AlarmServiceTest extends AbstractServiceTest {
alarms = alarmService.findAlarmsV2(tenantId, AlarmQueryV2.builder()
.affectedEntityId(childId)
.severityList(Arrays.asList(AlarmSeverity.CRITICAL))
.statusList(Arrays.asList(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.ACK)).pageLink(
.severityList(List.of(AlarmSeverity.CRITICAL))
.statusList(List.of(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.ACK)).pageLink(
new TimePageLink(1, 0, "",
new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis())
).build());
@ -310,8 +309,8 @@ public class AlarmServiceTest extends AbstractServiceTest {
// Check not existing relation
alarms = alarmService.findAlarmsV2(tenantId, AlarmQueryV2.builder()
.affectedEntityId(childId)
.severityList(Arrays.asList(AlarmSeverity.CRITICAL))
.statusList(Arrays.asList(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink(
.severityList(List.of(AlarmSeverity.CRITICAL))
.statusList(List.of(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.UNACK)).pageLink(
new TimePageLink(1, 0, "",
new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis())
).build());
@ -323,8 +322,8 @@ public class AlarmServiceTest extends AbstractServiceTest {
alarms = alarmService.findAlarmsV2(tenantId, AlarmQueryV2.builder()
.affectedEntityId(childId)
.severityList(Arrays.asList(AlarmSeverity.CRITICAL))
.statusList(Arrays.asList(AlarmSearchStatus.CLEARED, AlarmSearchStatus.ACK)).pageLink(
.severityList(List.of(AlarmSeverity.CRITICAL))
.statusList(List.of(AlarmSearchStatus.CLEARED, AlarmSearchStatus.ACK)).pageLink(
new TimePageLink(1, 0, "",
new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis())
).build());
@ -334,7 +333,7 @@ public class AlarmServiceTest extends AbstractServiceTest {
}
@Test
public void testFindAssignedAlarm() throws ExecutionException, InterruptedException {
public void testFindAssignedAlarm() {
AssetId parentId = new AssetId(Uuids.timeBased());
AssetId childId = new AssetId(Uuids.timeBased());
@ -368,7 +367,6 @@ public class AlarmServiceTest extends AbstractServiceTest {
PageData<AlarmInfo> alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.assigneeId(tenantUser.getId())
.fetchOriginator(true)
.pageLink(new TimePageLink(1, 0, "",
new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis())
).build());
@ -405,7 +403,7 @@ public class AlarmServiceTest extends AbstractServiceTest {
}
@Test
public void testFindCustomerAlarm() throws ExecutionException, InterruptedException {
public void testFindCustomerAlarm() {
Customer customer = new Customer();
customer.setTitle("TestCustomer");
customer.setTenantId(tenantId);
@ -451,10 +449,10 @@ public class AlarmServiceTest extends AbstractServiceTest {
pageLink.setStartTs(0L);
pageLink.setEndTs(System.currentTimeMillis());
pageLink.setSearchPropagatedAlarms(true);
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
pageLink.setSeverityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(List.of(AlarmSearchStatus.ACTIVE));
PageData<AlarmData> tenantAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Arrays.asList(tenantDevice.getId(), customerDevice.getId()));
PageData<AlarmData> tenantAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), List.of(tenantDevice.getId(), customerDevice.getId()));
Assert.assertEquals(2, tenantAlarms.getData().size());
PageData<AlarmData> customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(customerDevice.getId()));
@ -473,7 +471,7 @@ public class AlarmServiceTest extends AbstractServiceTest {
}
@Test
public void testFindPropagatedCustomerAssetAlarm() throws ExecutionException, InterruptedException {
public void testFindPropagatedCustomerAssetAlarm() {
Customer customer = new Customer();
customer.setTitle("TestCustomer");
customer.setTenantId(tenantId);
@ -525,8 +523,8 @@ public class AlarmServiceTest extends AbstractServiceTest {
pageLink.setStartTs(0L);
pageLink.setEndTs(System.currentTimeMillis());
pageLink.setSearchPropagatedAlarms(true);
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
pageLink.setSeverityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(List.of(AlarmSearchStatus.ACTIVE));
//TEST that propagated alarms are visible on the asset level.
PageData<AlarmData> customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(customerAsset.getId()));
@ -576,7 +574,7 @@ public class AlarmServiceTest extends AbstractServiceTest {
pageLink.setStartTs(0L);
pageLink.setEndTs(System.currentTimeMillis());
pageLink.setSearchPropagatedAlarms(true);
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setSeverityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(Collections.singletonList(AlarmSearchStatus.ACTIVE));
//TEST that propagated alarms are visible on the asset level.
@ -599,7 +597,7 @@ public class AlarmServiceTest extends AbstractServiceTest {
}
@Test
public void testFindHighestAlarmSeverity() throws ExecutionException, InterruptedException {
public void testFindHighestAlarmSeverity() {
Customer customer = new Customer();
customer.setTitle("TestCustomer");
customer.setTenantId(tenantId);
@ -679,8 +677,8 @@ public class AlarmServiceTest extends AbstractServiceTest {
pageLink.setStartTs(0L);
pageLink.setEndTs(System.currentTimeMillis());
pageLink.setSearchPropagatedAlarms(false);
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
pageLink.setSeverityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(List.of(AlarmSearchStatus.ACTIVE));
PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId));
@ -695,8 +693,8 @@ public class AlarmServiceTest extends AbstractServiceTest {
pageLink.setStartTs(0L);
pageLink.setEndTs(System.currentTimeMillis());
pageLink.setSearchPropagatedAlarms(false);
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
pageLink.setSeverityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(List.of(AlarmSearchStatus.ACTIVE));
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId));
Assert.assertNotNull(alarms.getData());
@ -722,8 +720,8 @@ public class AlarmServiceTest extends AbstractServiceTest {
pageLink.setStartTs(0L);
pageLink.setEndTs(System.currentTimeMillis());
pageLink.setSearchPropagatedAlarms(true);
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
pageLink.setSeverityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(List.of(AlarmSearchStatus.ACTIVE));
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId));
Assert.assertNotNull(alarms.getData());
@ -738,8 +736,8 @@ public class AlarmServiceTest extends AbstractServiceTest {
pageLink.setStartTs(0L);
pageLink.setEndTs(System.currentTimeMillis());
pageLink.setSearchPropagatedAlarms(true);
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
pageLink.setSeverityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(List.of(AlarmSearchStatus.ACTIVE));
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(parentId));
Assert.assertNotNull(alarms.getData());
@ -748,7 +746,6 @@ public class AlarmServiceTest extends AbstractServiceTest {
PageData<AlarmInfo> alarmsInfoData = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.affectedEntityId(childId)
.fetchOriginator(true)
.status(AlarmStatus.ACTIVE_UNACK).pageLink(
new TimePageLink(10, 0, "",
new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis())
@ -759,7 +756,6 @@ public class AlarmServiceTest extends AbstractServiceTest {
alarmsInfoData = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.affectedEntityId(parentId)
.fetchOriginator(true)
.status(AlarmStatus.ACTIVE_UNACK).pageLink(
new TimePageLink(10, 0, "",
new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis())
@ -770,7 +766,6 @@ public class AlarmServiceTest extends AbstractServiceTest {
alarmsInfoData = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.affectedEntityId(parentId2)
.fetchOriginator(true)
.status(AlarmStatus.ACTIVE_UNACK).pageLink(
new TimePageLink(10, 0, "",
new SortOrder("createdTime", SortOrder.Direction.DESC), 0L, System.currentTimeMillis())
@ -786,8 +781,8 @@ public class AlarmServiceTest extends AbstractServiceTest {
pageLink.setStartTs(0L);
pageLink.setEndTs(System.currentTimeMillis());
pageLink.setSearchPropagatedAlarms(true);
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
pageLink.setSeverityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(List.of(AlarmSearchStatus.ACTIVE));
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(parentId));
Assert.assertNotNull(alarms.getData());
@ -803,8 +798,8 @@ public class AlarmServiceTest extends AbstractServiceTest {
pageLink.setStartTs(0L);
pageLink.setEndTs(System.currentTimeMillis());
pageLink.setSearchPropagatedAlarms(true);
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
pageLink.setSeverityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(List.of(AlarmSearchStatus.ACTIVE));
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId));
Assert.assertNotNull(alarms.getData());
@ -813,7 +808,7 @@ public class AlarmServiceTest extends AbstractServiceTest {
}
@Test
public void testCountAlarmsUsingAlarmDataQuery() throws ExecutionException, InterruptedException {
public void testCountAlarmsUsingAlarmDataQuery() {
AssetId childId = new AssetId(Uuids.timeBased());
long ts = System.currentTimeMillis();
@ -829,7 +824,7 @@ public class AlarmServiceTest extends AbstractServiceTest {
.startTs(0L)
.endTs(System.currentTimeMillis())
.searchPropagatedAlarms(false)
.severityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING))
.severityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING))
.statusList(List.of(AlarmSearchStatus.ACTIVE))
.build();
@ -841,7 +836,7 @@ public class AlarmServiceTest extends AbstractServiceTest {
.startTs(0L)
.endTs(System.currentTimeMillis())
.searchPropagatedAlarms(true)
.severityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING))
.severityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING))
.statusList(List.of(AlarmSearchStatus.ACTIVE))
.build();
@ -866,7 +861,7 @@ public class AlarmServiceTest extends AbstractServiceTest {
.startTs(0L)
.endTs(System.currentTimeMillis())
.searchPropagatedAlarms(true)
.severityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING))
.severityList(List.of(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING))
.statusList(List.of(AlarmSearchStatus.ACTIVE, AlarmSearchStatus.CLEARED))
.build();
@ -939,6 +934,6 @@ public class AlarmServiceTest extends AbstractServiceTest {
).build());
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(0, alarms.getData().size());
}
}

24
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java

@ -27,10 +27,10 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.client.RestClientResponseException;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import org.springframework.web.util.UriComponentsBuilder;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.TbContext;
@ -132,6 +132,7 @@ public class TbHttpClient {
this.webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.defaultHeader(HttpHeaders.CONNECTION, "close") //In previous realization this header was present! (Added for hotfix "Connection reset")
.build();
} catch (SSLException e) {
throw new TbNodeException(e);
@ -170,7 +171,7 @@ public class TbHttpClient {
BiConsumer<TbMsg, Throwable> onFailure) {
try {
if (semaphore != null && !semaphore.tryAcquire(config.getReadTimeoutMs(), TimeUnit.MILLISECONDS)) {
ctx.tellFailure(msg, new RuntimeException("Timeout during waiting for reply!"));
onFailure.accept(msg, new RuntimeException("Timeout during waiting for reply!"));
return;
}
@ -183,10 +184,10 @@ public class TbHttpClient {
.uri(uri)
.headers(headers -> prepareHeaders(headers, msg));
if (HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method) ||
HttpMethod.PATCH.equals(method) || HttpMethod.DELETE.equals(method) ||
if ((HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method) ||
HttpMethod.PATCH.equals(method) || HttpMethod.DELETE.equals(method)) &&
!config.isIgnoreRequestBody()) {
request.body(BodyInserters.fromValue(getData(msg, config.isIgnoreRequestBody(), config.isParseToPlainText())));
request.body(BodyInserters.fromValue(getData(msg, config.isParseToPlainText())));
}
request
@ -236,11 +237,9 @@ public class TbHttpClient {
return uri;
}
private String getData(TbMsg tbMsg, boolean ignoreBody, boolean parseToPlainText) {
if (!ignoreBody && parseToPlainText) {
return JacksonUtil.toPlainText(tbMsg.getData());
}
return tbMsg.getData();
private Object getData(TbMsg tbMsg, boolean parseToPlainText) {
String data = tbMsg.getData();
return parseToPlainText ? JacksonUtil.toPlainText(data) : JacksonUtil.toJsonNode(data);
}
private TbMsg processResponse(TbContext ctx, TbMsg origMsg, ResponseEntity<String> response) {
@ -283,10 +282,9 @@ public class TbHttpClient {
private TbMsg processException(TbMsg origMsg, Throwable e) {
TbMsgMetaData metaData = origMsg.getMetaData();
metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage());
if (e instanceof RestClientResponseException) {
RestClientResponseException restClientResponseException = (RestClientResponseException) e;
if (e instanceof WebClientResponseException restClientResponseException) {
metaData.putValue(STATUS, restClientResponseException.getStatusText());
metaData.putValue(STATUS_CODE, restClientResponseException.getRawStatusCode() + "");
metaData.putValue(STATUS_CODE, restClientResponseException.getStatusCode().value() + "");
metaData.putValue(ERROR_BODY, restClientResponseException.getResponseBodyAsString());
}
return TbMsg.transformMsgMetadata(origMsg, metaData);

2
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java

@ -168,7 +168,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest {
try {
assertEquals(path, request.getRequestLine().getUri(), "Request path matches");
assertTrue(request.containsHeader("Content-Type"), "Content-Type included");
assertEquals("text/plain;charset=UTF-8",
assertEquals("application/json",
request.getFirstHeader("Content-Type").getValue(), "Content-Type value");
assertTrue(request.containsHeader("Content-Length"), "Content-Length included");
assertEquals("2",

4
ui-ngx/src/app/core/services/utils.service.ts

@ -456,6 +456,10 @@ export class UtilsService {
return isDefined(value);
}
public isDefinedAndNotNull(value: any): boolean {
return isDefinedAndNotNull(value);
}
public defaultValue(value: any, defaultValue: any): any {
if (isDefinedAndNotNull(value)) {
return value;

2
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html

@ -160,7 +160,7 @@
</div>
<tb-chart-fill-settings
formControlName="barBackgroundSettings"
title="widgets.chart.background"
titleText="widgets.chart.background"
fillNoneTitle="widgets.chart.fill-type-solid">
</tb-chart-fill-settings>
<tb-time-series-no-aggregation-bar-width-settings

24
ui-ngx/src/app/modules/home/components/widget/lib/cards/unread-notification-widget.component.ts

@ -203,23 +203,29 @@ export class UnreadNotificationWidgetComponent implements OnInit, OnDestroy {
}
markAsRead(id: string) {
const cmd = NotificationSubscriber.createMarkAsReadCommand(this.notificationWsService, [id]);
cmd.subscribe();
if (!this.ctx.isEdit && !this.ctx.isPreview) {
const cmd = NotificationSubscriber.createMarkAsReadCommand(this.notificationWsService, [id]);
cmd.subscribe();
}
}
markAsAllRead($event: Event) {
if ($event) {
$event.stopPropagation();
if (!this.ctx.isEdit && !this.ctx.isPreview) {
if ($event) {
$event.stopPropagation();
}
const cmd = NotificationSubscriber.createMarkAllAsReadCommand(this.notificationWsService);
cmd.subscribe();
}
const cmd = NotificationSubscriber.createMarkAllAsReadCommand(this.notificationWsService);
cmd.subscribe();
}
viewAll($event: Event) {
if ($event) {
$event.stopPropagation();
if (!this.ctx.isEdit && !this.ctx.isPreview) {
if ($event) {
$event.stopPropagation();
}
this.router.navigateByUrl(this.router.parseUrl('/notification/inbox')).then(() => {});
}
this.router.navigateByUrl(this.router.parseUrl('/notification/inbox')).then(() => {});
}
trackById(index: number, item: NotificationRequest): string {

2
ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts

@ -169,6 +169,6 @@ export class TimeSeriesChartWidgetComponent implements OnInit, OnDestroy, AfterV
}
public toggleLegendKey(legendKey: LegendKey) {
this.timeSeriesChart.toggleKey(legendKey.dataKey);
this.timeSeriesChart.toggleKey(legendKey.dataKey, legendKey.dataIndex);
}
}

14
ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts

@ -56,7 +56,14 @@ import {
measureAxisNameSize
} from '@home/components/widget/lib/chart/echarts-widget.models';
import { DateFormatProcessor, ValueSourceType } from '@shared/models/widget-settings.models';
import { formattedDataFormDatasourceData, formatValue, isDefinedAndNotNull, isEqual, mergeDeep } from '@core/utils';
import {
formattedDataFormDatasourceData,
formatValue,
isDefined,
isDefinedAndNotNull,
isEqual,
mergeDeep
} from '@core/utils';
import { DataKey, Datasource, DatasourceType, FormattedData, widgetType } from '@shared/models/widget.models';
import * as echarts from 'echarts/core';
import { CallbackDataParams, PiecewiseVisualMapOption } from 'echarts/types/dist/shared';
@ -300,7 +307,7 @@ export class TbTimeSeriesChart {
}
}
public toggleKey(dataKey: DataKey): void {
public toggleKey(dataKey: DataKey, dataIndex?: number): void {
const enable = dataKey.hidden;
const dataItem = this.dataItems.find(d => d.dataKey === dataKey);
if (dataItem) {
@ -320,6 +327,9 @@ export class TbTimeSeriesChart {
this.timeSeriesChart.setOption(this.timeSeriesChartOptions, this.stackMode ? {notMerge: true} : {replaceMerge: mergeList});
this.updateAxes();
dataKey.hidden = !enable;
if (isDefined(dataIndex)) {
this.ctx.defaultSubscription.updateDataVisibility(dataIndex);
}
if (enable) {
this.timeSeriesChart.dispatchAction({
type: 'highlight',

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html

@ -79,7 +79,7 @@
</div>
<tb-chart-fill-settings
formControlName="barBackgroundSettings"
title="widgets.chart.background"
titleText="widgets.chart.background"
fillNoneTitle="widgets.chart.fill-type-solid">
</tb-chart-fill-settings>
<tb-time-series-no-aggregation-bar-width-settings

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-bar-settings.component.html

@ -77,7 +77,7 @@
</div>
<tb-chart-fill-settings
formControlName="backgroundSettings"
title="widgets.chart.background"
titleText="widgets.chart.background"
fillNoneTitle="widgets.chart.fill-type-solid">
</tb-chart-fill-settings>
</ng-container>

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.html

@ -18,7 +18,7 @@
<ng-container [formGroup]="fillSettingsFormGroup">
<div class="tb-form-row column">
<div class="tb-form-row no-border no-padding space-between">
<div>{{ title | translate }}</div>
<div>{{ titleText | translate }}</div>
<tb-toggle-select formControlName="type">
<tb-toggle-option *ngFor="let type of chartFillTypes" [value]="type">{{ chartFillTypeTranslationMap.get(type) | translate }}</tb-toggle-option>
</tb-toggle-select>

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/chart-fill-settings.component.ts

@ -55,7 +55,7 @@ export class ChartFillSettingsComponent implements OnInit, ControlValueAccessor
disabled: boolean;
@Input()
title = 'widgets.chart.fill';
titleText = 'widgets.chart.fill';
@Input()
fillNoneTitle = 'widgets.chart.fill-type-none';

1
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -487,6 +487,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
}
if (!this.widgetContext.inited && this.isReady()) {
this.widgetContext.inited = true;
this.widgetContext.destroyed = false;
this.dashboardWidget.updateWidgetParams();
this.widgetContext.detectContainerChanges();
if (this.cafs.init) {

2
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -449,6 +449,8 @@ export class WidgetContext {
labelPattern.destroy();
}
this.labelPatterns.clear();
this.width = undefined;
this.height = undefined;
this.destroyed = true;
}

49
ui-ngx/src/app/shared/components/entity/entity-list.component.ts

@ -25,11 +25,17 @@ import {
SimpleChanges,
ViewChild
} from '@angular/core';
import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import {
ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormGroup,
ValidationErrors,
Validators
} from '@angular/forms';
import { Observable } from 'rxjs';
import { filter, map, mergeMap, share, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { EntityType } from '@shared/models/entity-type.models';
import { BaseData } from '@shared/models/base-data';
@ -49,6 +55,11 @@ import { SubscriptSizing } from '@angular/material/form-field';
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => EntityListComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => EntityListComponent),
multi: true
}
]
})
@ -56,7 +67,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
entityListFormGroup: UntypedFormGroup;
modelValue: Array<string> | null;
private modelValue: Array<string> | null;
@Input()
entityType: EntityType;
@ -108,17 +119,16 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
public translate: TranslateService,
constructor(public translate: TranslateService,
private entityService: EntityService,
private fb: UntypedFormBuilder) {
this.entityListFormGroup = this.fb.group({
entities: [this.entities, this.required ? [Validators.required] : []],
entities: [this.entities],
entity: [null]
});
}
updateValidators() {
private updateValidators() {
this.entityListFormGroup.get('entities').setValidators(this.required ? [Validators.required] : []);
this.entityListFormGroup.get('entities').updateValueAndValidity();
}
@ -189,7 +199,13 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
this.dirty = true;
}
reset() {
validate(): ValidationErrors | null {
return this.entityListFormGroup.valid ? null : {
entities: {valid: false}
};
}
private reset() {
this.entities = [];
this.entityListFormGroup.get('entities').setValue(this.entities);
this.modelValue = null;
@ -201,7 +217,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
this.dirty = true;
}
add(entity: BaseData<EntityId>): void {
private add(entity: BaseData<EntityId>): void {
if (!this.modelValue || this.modelValue.indexOf(entity.id.id) === -1) {
if (!this.modelValue) {
this.modelValue = [];
@ -214,7 +230,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
this.clear();
}
remove(entity: BaseData<EntityId>) {
public remove(entity: BaseData<EntityId>) {
let index = this.entities.indexOf(entity);
if (index >= 0) {
this.entities.splice(index, 1);
@ -229,11 +245,11 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
}
}
displayEntityFn(entity?: BaseData<EntityId>): string | undefined {
public displayEntityFn(entity?: BaseData<EntityId>): string | undefined {
return entity ? entity.name : undefined;
}
fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> {
private fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> {
this.searchText = searchText;
return this.entityService.getEntitiesByNameFilter(this.entityType, searchText,
@ -241,14 +257,14 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
map((data) => data ? data : []));
}
onFocus() {
public onFocus() {
if (this.dirty) {
this.entityListFormGroup.get('entity').updateValueAndValidity({onlySelf: true, emitEvent: true});
this.dirty = false;
}
}
clear(value: string = '') {
private clear(value: string = '') {
this.entityInput.nativeElement.value = value;
this.entityListFormGroup.get('entity').patchValue(value, {emitEvent: true});
setTimeout(() => {
@ -257,8 +273,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
}, 0);
}
textIsNotEmpty(text: string): boolean {
public textIsNotEmpty(text: string): boolean {
return (text && text.length > 0);
}
}

Loading…
Cancel
Save