diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java index 5c95a0e98f..5671ffb2ab 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java @@ -335,6 +335,9 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i Edge edge = edgeGrpcSession.getEdge(); TenantId tenantId = edge.getTenantId(); log.info("[{}][{}] edge [{}] connected successfully.", tenantId, edgeGrpcSession.getSessionId(), edgeId); + if (sessions.containsKey(edgeId)) { + destroySession(sessions.get(edgeId)); + } sessions.put(edgeId, edgeGrpcSession); final Lock newEventLock = sessionNewEventsLocks.computeIfAbsent(edgeId, id -> new ReentrantLock()); newEventLock.lock(); @@ -503,7 +506,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i } finally { newEventLock.unlock(); } - toRemove.destroy(); + destroySession(toRemove); TenantId tenantId = toRemove.getEdge().getTenantId(); save(tenantId, edgeId, ACTIVITY_STATE, false); long lastDisconnectTs = System.currentTimeMillis(); @@ -516,6 +519,12 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i edgeIdServiceIdCache.evict(edgeId); } + private void destroySession(EdgeGrpcSession session) { + try (session) { + session.destroy(); + } + } + private void save(TenantId tenantId, EdgeId edgeId, String key, long value) { log.debug("[{}][{}] Updating long edge telemetry [{}] [{}]", tenantId, edgeId, key, value); if (persistToTelemetry) { diff --git a/application/src/test/java/org/thingsboard/server/service/job/JobManagerTest.java b/application/src/test/java/org/thingsboard/server/service/job/JobManagerTest.java index 8da1be43f1..af499ade31 100644 --- a/application/src/test/java/org/thingsboard/server/service/job/JobManagerTest.java +++ b/application/src/test/java/org/thingsboard/server/service/job/JobManagerTest.java @@ -165,7 +165,6 @@ public class JobManagerTest extends AbstractControllerTest { await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> { Job job = findJobById(jobId); assertThat(job.getStatus()).isEqualTo(JobStatus.CANCELLED); - assertThat(job.getResult().getSuccessfulCount()).isBetween(1, tasksCount - 1); assertThat(job.getResult().getDiscardedCount()).isBetween(1, tasksCount - 1); assertThat(job.getResult().getTotalCount()).isEqualTo(tasksCount); assertThat(job.getResult().getCompletedCount()).isEqualTo(tasksCount); diff --git a/common/edqs/src/main/java/org/thingsboard/server/edqs/query/processor/AbstractEntityProfileNameQueryProcessor.java b/common/edqs/src/main/java/org/thingsboard/server/edqs/query/processor/AbstractEntityProfileNameQueryProcessor.java index b78e49879e..f881d616ef 100644 --- a/common/edqs/src/main/java/org/thingsboard/server/edqs/query/processor/AbstractEntityProfileNameQueryProcessor.java +++ b/common/edqs/src/main/java/org/thingsboard/server/edqs/query/processor/AbstractEntityProfileNameQueryProcessor.java @@ -36,7 +36,7 @@ public abstract class AbstractEntityProfileNameQueryProcessor(getProfileNames(this.filter)); - pattern = RepositoryUtils.toSqlLikePattern(getEntityNameFilter(filter)); + pattern = RepositoryUtils.toContainsSqlLikePattern(getEntityNameFilter(filter)); } protected abstract String getEntityNameFilter(T filter); diff --git a/common/edqs/src/main/java/org/thingsboard/server/edqs/query/processor/AbstractEntityProfileQueryProcessor.java b/common/edqs/src/main/java/org/thingsboard/server/edqs/query/processor/AbstractEntityProfileQueryProcessor.java index 301ead7c63..9d043ff6fc 100644 --- a/common/edqs/src/main/java/org/thingsboard/server/edqs/query/processor/AbstractEntityProfileQueryProcessor.java +++ b/common/edqs/src/main/java/org/thingsboard/server/edqs/query/processor/AbstractEntityProfileQueryProcessor.java @@ -43,7 +43,7 @@ public abstract class AbstractEntityProfileQueryProcessor ext private Set> getEntitiesSet(RelationsRepo relations) { Set> result = new HashSet<>(); - Set processed = new HashSet<>(); + Set processed = new HashSet<>(); Queue tasks = new LinkedList<>(); int maxLvl = getMaxLevel() == 0 ? MAXIMUM_QUERY_LEVEL : Math.max(1, getMaxLevel()); for (UUID uuid : getRootEntities()) { @@ -114,7 +115,7 @@ public abstract class AbstractRelationQueryProcessor ext } while (!tasks.isEmpty()) { RelationSearchTask task = tasks.poll(); - if (processed.add(task.entityId)) { + if (processed.add(task)) { var entityLvl = task.lvl + 1; Set entities = EntitySearchDirection.FROM.equals(getDirection()) ? relations.getFrom(task.entityId) : relations.getTo(task.entityId); if (isFetchLastLevelOnly() && entities.isEmpty() && task.previous != null && check(task.previous)) { @@ -157,6 +158,7 @@ public abstract class AbstractRelationQueryProcessor ext protected abstract boolean check(RelationInfo relationInfo); @RequiredArgsConstructor + @EqualsAndHashCode private static class RelationSearchTask { private final UUID entityId; private final int lvl; diff --git a/common/edqs/src/main/java/org/thingsboard/server/edqs/query/processor/EntityNameQueryProcessor.java b/common/edqs/src/main/java/org/thingsboard/server/edqs/query/processor/EntityNameQueryProcessor.java index fbb36cf2a0..ffb2a4c33d 100644 --- a/common/edqs/src/main/java/org/thingsboard/server/edqs/query/processor/EntityNameQueryProcessor.java +++ b/common/edqs/src/main/java/org/thingsboard/server/edqs/query/processor/EntityNameQueryProcessor.java @@ -30,7 +30,7 @@ public class EntityNameQueryProcessor extends AbstractSimpleQueryProcessor value.equals(predicateValue); - case STARTS_WITH -> value.startsWith(predicateValue); - case ENDS_WITH -> value.endsWith(predicateValue); + case STARTS_WITH -> toStartsWithSqlLikePattern(predicateValue).matcher(value).matches(); + case ENDS_WITH -> toEndsWithSqlLikePattern(predicateValue).matcher(value).matches(); case NOT_EQUAL -> !value.equals(predicateValue); - case CONTAINS -> value.contains(predicateValue); - case NOT_CONTAINS -> !value.contains(predicateValue); + case CONTAINS -> toContainsSqlLikePattern(predicateValue).matcher(value).matches(); + case NOT_CONTAINS -> !toContainsSqlLikePattern(predicateValue).matcher(value).matches(); case IN -> equalsAny(value, splitByCommaWithoutQuotes(predicateValue)); case NOT_IN -> !equalsAny(value, splitByCommaWithoutQuotes(predicateValue)); }; @@ -304,6 +303,15 @@ public class RepositoryUtils { return true; } else if (filterPredicates.getOperation() == OR) { for (KeyFilterPredicate filterPredicate : filterPredicates.getPredicates()) { + + // Emulate the SQL-like behavior of ThingsBoard's Entity Data Query service: + // for COMPLEX filters, return no results if filter value is empty + if (filterPredicate instanceof StringFilterPredicate stringFilterPredicate) { + if (StringUtils.isEmpty(stringFilterPredicate.getValue().getValue())) { + continue; + } + } + if (simpleKeyFilter.check(value, filterPredicate)) { return true; } @@ -314,25 +322,40 @@ public class RepositoryUtils { } } - public static Pattern toSqlLikePattern(String nameFilter) { - if (StringUtils.isNotBlank(nameFilter)) { - boolean percentSymbolOnStart = nameFilter.startsWith("%"); - boolean percentSymbolOnEnd = nameFilter.endsWith("%"); - if (percentSymbolOnStart) { - nameFilter = nameFilter.substring(1); - } - if (percentSymbolOnEnd) { - nameFilter = nameFilter.substring(0, nameFilter.length() - 1); - } - if (percentSymbolOnStart || percentSymbolOnEnd) { - return Pattern.compile((percentSymbolOnStart ? ".*" : "") + Pattern.quote(nameFilter) + (percentSymbolOnEnd ? ".*" : ""), Pattern.CASE_INSENSITIVE); - } else { - return Pattern.compile(Pattern.quote(nameFilter) + ".*", Pattern.CASE_INSENSITIVE); - } + public static Pattern toContainsSqlLikePattern(String filter) { + if (StringUtils.isNotBlank(filter)) { + return toSqlLikePattern(filter, ".*", ".*"); } return null; } + private static Pattern toStartsWithSqlLikePattern(String filter) { + return toSqlLikePattern(filter, "^", ".*"); + } + + private static Pattern toEndsWithSqlLikePattern(String filter) { + return toSqlLikePattern(filter, ".*", "$"); + } + + private static Pattern toSqlLikePattern(String value, String prefix, String suffix) { + if (value.contains("%") || value.contains("_")) { + String regexValue = value + .replace("_", ".") + .replace("%", ".*"); + String regex; + if ("^".equals(prefix)) { + regex = "^" + regexValue + (regexValue.endsWith(".*") ? "" : ".*"); + } else if ("$".equals(suffix)) { + regex = (regexValue.startsWith(".*") ? "" : ".*") + regexValue + "$"; + } else { + regex = (regexValue.startsWith(".*") ? "" : ".*") + regexValue + (regexValue.endsWith(".*") ? "" : ".*"); + } + return Pattern.compile(regex); + } else { + return Pattern.compile(prefix + Pattern.quote(value) + suffix); + } + } + @FunctionalInterface public interface SimpleKeyFilter { diff --git a/edqs/src/test/java/org/thingsboard/server/edqs/repo/RepositoryUtilsTest.java b/edqs/src/test/java/org/thingsboard/server/edqs/repo/RepositoryUtilsTest.java index 6c7444c92a..fa3784ca19 100644 --- a/edqs/src/test/java/org/thingsboard/server/edqs/repo/RepositoryUtilsTest.java +++ b/edqs/src/test/java/org/thingsboard/server/edqs/repo/RepositoryUtilsTest.java @@ -70,7 +70,45 @@ public class RepositoryUtilsTest { Arguments.of("loranet 123", getNameFilter(StringOperation.IN, "loranet 123, loranet 124"), true), Arguments.of("loranet 123", getNameFilter(StringOperation.IN, "loranet 125, loranet 126"), false), Arguments.of("loranet 123", getNameFilter(StringOperation.NOT_IN, "loranet 125, loranet 126"), true), - Arguments.of("loranet 123", getNameFilter(StringOperation.NOT_IN, "loranet 123, loranet 126"), false) + Arguments.of("loranet 123", getNameFilter(StringOperation.NOT_IN, "loranet 123, loranet 126"), false), + + // Basic CONTAINS + Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "%loranet"), true), + Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "loranet%"), true), + Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "%ranet%"), true), + Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "%123"), true), + Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "%loranx%"), false), + + // Basic STARTS_WITH + Arguments.of("loranet 123", getNameFilter(StringOperation.STARTS_WITH, "loranet%"), true), + Arguments.of("loranet 123", getNameFilter(StringOperation.STARTS_WITH, "lora%"), true), + Arguments.of("loranet 123", getNameFilter(StringOperation.STARTS_WITH, "lorax%"), false), + + // Basic ENDS_WITH + Arguments.of("loranet 123", getNameFilter(StringOperation.ENDS_WITH, "%123"), true), + Arguments.of("loranet 123", getNameFilter(StringOperation.ENDS_WITH, "%23"), true), + Arguments.of("loranet 123", getNameFilter(StringOperation.ENDS_WITH, "%124"), false), + + // CONTAINS with _ + Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "loranet_123"), true), // '_' = ' ' + Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "loranet_12_"), true), + Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "loran_t%"), true), + + // STARTS_WITH with _ + Arguments.of("loranet 123", getNameFilter(StringOperation.STARTS_WITH, "loranet_"), true), + Arguments.of("loranet 123", getNameFilter(StringOperation.STARTS_WITH, "lora__t%"), true), + Arguments.of("loranet 123", getNameFilter(StringOperation.STARTS_WITH, "lor_net%"), true), + + // ENDS_WITH with _ + Arguments.of("loranet 123", getNameFilter(StringOperation.ENDS_WITH, "_23"), true), + Arguments.of("loranet 123", getNameFilter(StringOperation.ENDS_WITH, "_2_"), true), + Arguments.of("loranet 123", getNameFilter(StringOperation.ENDS_WITH, "_3"), true), + + // Mixed patterns + Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "lora__t 1%"), true), + Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "lora%net%3"), true), + Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "%o_anet%2_3"), false), + Arguments.of("loranet 123", getNameFilter(StringOperation.CONTAINS, "lora___ ___"), true) ); } diff --git a/pom.xml b/pom.xml index 05ca24f06f..3e59260543 100755 --- a/pom.xml +++ b/pom.xml @@ -54,7 +54,7 @@ 0.12.5 2.0.17 2.24.3 - 1.5.18 + 1.5.6 0.10 4.17.0 4.2.25 diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/credentials-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/credentials-config.component.html index e0d1714e2c..7f7d50db3e 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/common/credentials-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/credentials-config.component.html @@ -16,7 +16,7 @@ -->
- + rule-node-config.credentials