diff --git a/application/pom.xml b/application/pom.xml index 33b21b3243..3693fe6dbe 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT thingsboard application diff --git a/application/src/main/data/upgrade/3.4.0/schema_update.sql b/application/src/main/data/upgrade/3.4.0/schema_update.sql index 12a86acebc..71588d88ac 100644 --- a/application/src/main/data/upgrade/3.4.0/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.0/schema_update.sql @@ -228,3 +228,7 @@ BEGIN ON CONFLICT DO NOTHING; END $$; + +UPDATE tb_user + SET additional_info = REPLACE(additional_info, '"lang":"ja_JA"', '"lang":"ja_JP"') + WHERE additional_info LIKE '%"lang":"ja_JA"%'; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index d5460689b8..92fdfc6e48 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -19,7 +19,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.netty.channel.EventLoopGroup; import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; @@ -39,6 +38,7 @@ import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.asset.Asset; @@ -46,7 +46,6 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; diff --git a/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java index bbeed33449..360d5e54c0 100644 --- a/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java +++ b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java @@ -33,9 +33,9 @@ import org.springframework.security.web.util.UrlUtils; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.service.security.auth.oauth2.TbOAuth2ParameterNames; diff --git a/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java b/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java index 86675a79a8..ae6ce20baf 100644 --- a/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java +++ b/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java @@ -16,7 +16,6 @@ package org.thingsboard.server.config; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.security.authentication.BadCredentialsException; @@ -24,6 +23,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.filter.GenericFilterBean; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index e58b00f9e6..c53cf1b504 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -17,7 +17,6 @@ package org.thingsboard.server.config; import com.fasterxml.classmate.TypeResolver; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -333,7 +332,7 @@ public class SwaggerConfiguration { errorResponse("401 ", "Unauthorized (**Expired credentials**)", List.of( errorExample("credentials-expired", "Expired credentials", - ThingsboardCredentialsExpiredResponse.of("User password expired!", RandomStringUtils.randomAlphanumeric(30))) + ThingsboardCredentialsExpiredResponse.of("User password expired!", StringUtils.randomAlphanumeric(30))) ), ThingsboardCredentialsExpiredResponse.class ) ); diff --git a/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java b/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java index 965975c0ee..e5a217893e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java @@ -22,10 +22,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.util.StringUtils; import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index d2e11086c7..858a7ee3c4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -18,7 +18,6 @@ package org.thingsboard.server.controller; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; @@ -30,6 +29,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; diff --git a/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java b/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java index db78b87f70..33792a703f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java @@ -17,7 +17,6 @@ package org.thingsboard.server.controller; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; -import org.apache.commons.lang3.StringUtils; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; @@ -26,6 +25,7 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.exception.ThingsboardException; diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 7d2d2cd255..1e9958cc25 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -23,7 +23,6 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.support.DefaultMessageSourceResolvable; @@ -47,6 +46,7 @@ import org.thingsboard.server.common.data.EntityViewInfo; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.Tenant; @@ -84,7 +84,6 @@ import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetsBundleId; -import org.thingsboard.server.common.data.page.PageDataIterableByTenantIdEntityId; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; @@ -146,15 +145,12 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import javax.mail.MessagingException; import javax.servlet.http.HttpServletResponse; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; -import static org.thingsboard.server.controller.ControllerConstants.DEFAULT_PAGE_SIZE; import static org.thingsboard.server.controller.ControllerConstants.INCORRECT_TENANT_ID; import static org.thingsboard.server.controller.UserController.YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION; import static org.thingsboard.server.dao.service.Validator.validateId; diff --git a/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java index b8e5da25de..31f64254af 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java +++ b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java @@ -17,7 +17,6 @@ package org.thingsboard.server.controller; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; -import org.apache.commons.lang3.StringUtils; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -25,6 +24,7 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentType; diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index e45a195a2d..0baf89e0dc 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -19,7 +19,6 @@ import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; @@ -33,6 +32,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.page.PageData; diff --git a/application/src/main/java/org/thingsboard/server/controller/EventController.java b/application/src/main/java/org/thingsboard/server/controller/EventController.java index 7dd82471c6..db040599fa 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EventController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EventController.java @@ -148,8 +148,11 @@ public class EventController extends BaseController { return checkNotNull(eventService.findEvents(tenantId, entityId, resolveEventType(eventType), pageLink)); } - @ApiOperation(value = "Get Events (getEvents)", - notes = "Returns a page of events for specified entity. " + + @ApiOperation(value = "Get Events (Deprecated)", + notes = "Returns a page of events for specified entity. Deprecated and will be removed in next minor release. " + + "The call was deprecated to improve the performance of the system. " + + "Current implementation will return 'Lifecycle' events only. " + + "Use 'Get events by type' or 'Get events by filter' instead. " + PAGE_DATA_PARAMETERS, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.GET) diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index b9c929f909..4498b51c6c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -35,7 +35,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -49,6 +48,7 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index 3565af1ab0..432b35fd80 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -16,7 +16,6 @@ package org.thingsboard.server.controller.plugin; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.BeanCreationNotAllowedException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -28,6 +27,7 @@ import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.adapter.NativeWebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; @@ -49,7 +49,6 @@ import javax.websocket.SendResult; import javax.websocket.Session; import java.io.IOException; import java.net.URI; -import java.nio.ByteBuffer; import java.security.InvalidParameterException; import java.util.Queue; import java.util.Set; diff --git a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java index 39064c210c..79968187b6 100644 --- a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java +++ b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java @@ -20,13 +20,13 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType; @@ -41,7 +41,6 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.audit.AuditLogService; -import org.thingsboard.server.queue.util.TbCoreComponent; import java.util.List; import java.util.Map; @@ -199,7 +198,7 @@ public class EntityActionService { } } TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode)); - if (tenantId.isNullUid()) { + if (tenantId == null || tenantId.isNullUid()) { if (entity instanceof HasTenantId) { tenantId = ((HasTenantId) entity).getTenantId(); } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultRateLimitService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultRateLimitService.java index 55b23a99cc..525386d884 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultRateLimitService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultRateLimitService.java @@ -16,8 +16,8 @@ package org.thingsboard.server.service.apiusage; import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.msg.tools.TbRateLimits; diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index d5c86a67db..f6a590eee2 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -16,10 +16,8 @@ package org.thingsboard.server.service.apiusage; import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -34,6 +32,7 @@ import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageStateMailMessage; import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -80,7 +79,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; diff --git a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java index 68c9e3117e..a397a4d0e9 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java @@ -27,13 +27,13 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java index 8b2126905f..01f7fa282e 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java @@ -21,8 +21,6 @@ import com.fasterxml.jackson.databind.node.TextNode; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang3.RandomStringUtils; -import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; @@ -31,6 +29,7 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; @@ -44,15 +43,15 @@ import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingC import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.sync.ie.importing.csv.AbstractBulkImportService; -import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType; import org.thingsboard.server.service.entitiy.device.TbDeviceService; import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.sync.ie.importing.csv.AbstractBulkImportService; import java.util.Collection; import java.util.Collections; @@ -157,7 +156,7 @@ public class DeviceBulkImportService extends AbstractBulkImportService { private void setUpAccessTokenCredentials(Map fields, DeviceCredentials credentials) { credentials.setCredentialsId(Optional.ofNullable(fields.get(BulkImportColumnType.ACCESS_TOKEN)) - .orElseGet(() -> RandomStringUtils.randomAlphanumeric(20))); + .orElseGet(() -> StringUtils.randomAlphanumeric(20))); } private void setUpBasicMqttCredentials(Map fields, DeviceCredentials credentials) { diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 7fd42740c4..c88cbd8156 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -20,14 +20,14 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; @@ -51,7 +51,6 @@ import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.queue.TbQueueCallback; @@ -189,7 +188,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private ProvisionResponse processCreateDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { try { if (StringUtils.isEmpty(provisionRequest.getDeviceName())) { - String newDeviceName = RandomStringUtils.randomAlphanumeric(20); + String newDeviceName = StringUtils.randomAlphanumeric(20); log.info("Device name not found in provision request. Generated name is: {}", newDeviceName); provisionRequest.setDeviceName(newDeviceName); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java index d7e861a9fb..29ff49d626 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java @@ -20,6 +20,7 @@ import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.dashboard.DashboardService; @@ -61,6 +62,9 @@ import org.thingsboard.server.service.executors.GrpcCallbackExecutorService; @Lazy public class EdgeContextComponent { + @Autowired + private TbClusterService clusterService; + @Autowired private EdgeService edgeService; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index 2f9b4c0498..3f8950f281 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -211,6 +211,7 @@ public final class EdgeGrpcSession implements Closeable { @Override public void onSuccess(Void result) { syncCompleted = true; + ctx.getClusterService().onEdgeEventUpdate(edge.getTenantId(), edge.getId()); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java index c8fdcc4537..3c2db96b31 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java @@ -16,7 +16,8 @@ package org.thingsboard.server.service.edge.rpc.constructor; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.protobuf.ByteString; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; @@ -28,6 +29,7 @@ import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.RpcRequestMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; import java.util.UUID; @@ -36,7 +38,8 @@ import java.util.UUID; @TbCoreComponent public class DeviceMsgConstructor { - protected static final ObjectMapper mapper = new ObjectMapper(); + @Autowired + private DataDecodingEncodingService dataDecodingEncodingService; public DeviceUpdateMsg constructDeviceUpdatedMsg(UpdateMsgType msgType, Device device, CustomerId customerId, String conflictName) { DeviceUpdateMsg.Builder builder = DeviceUpdateMsg.newBuilder() @@ -66,6 +69,9 @@ public class DeviceMsgConstructor { if (conflictName != null) { builder.setConflictName(conflictName); } + if (device.getDeviceData() != null) { + builder.setDeviceDataBytes(ByteString.copyFrom(dataDecodingEncodingService.encode(device.getDeviceData()))); + } return builder.build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java index ee10c7859b..c86fb98502 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java @@ -23,10 +23,10 @@ import freemarker.template.Configuration; import freemarker.template.Template; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.text.WordUtils; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java index 0e1dee7132..6d0f66a884 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java @@ -22,14 +22,15 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; -import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; @@ -60,9 +61,11 @@ import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueMsgMetadata; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.locks.ReentrantLock; @@ -71,6 +74,9 @@ import java.util.concurrent.locks.ReentrantLock; @TbCoreComponent public class DeviceEdgeProcessor extends BaseEdgeProcessor { + @Autowired + private DataDecodingEncodingService dataDecodingEncodingService; + private static final ReentrantLock deviceCreationLock = new ReentrantLock(); public ListenableFuture processDeviceFromEdge(TenantId tenantId, Edge edge, DeviceUpdateMsg deviceUpdateMsg) { @@ -88,7 +94,7 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { } else { log.info("[{}] Device with name '{}' already exists on the cloud, but not related to this edge [{}]. deviceUpdateMsg [{}]." + "Creating a new device with random prefix and relate to this edge", tenantId, deviceName, edge.getId(), deviceUpdateMsg); - String newDeviceName = deviceUpdateMsg.getName() + "_" + RandomStringUtils.randomAlphabetic(15); + String newDeviceName = deviceUpdateMsg.getName() + "_" + StringUtils.randomAlphabetic(15); Device newDevice; try { newDevice = createDevice(tenantId, edge, deviceUpdateMsg, newDeviceName); @@ -192,6 +198,11 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { deviceUpdateMsg.getDeviceProfileIdLSB())); device.setDeviceProfileId(deviceProfileId); } + Optional deviceDataOpt = + dataDecodingEncodingService.decode(deviceUpdateMsg.getDeviceDataBytes().toByteArray()); + if (deviceDataOpt.isPresent()) { + device.setDeviceData(deviceDataOpt.get()); + } Device savedDevice = deviceService.saveDevice(device); tbClusterService.onDeviceUpdated(savedDevice, device); return saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, deviceId, null); @@ -232,6 +243,11 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { deviceUpdateMsg.getDeviceProfileIdLSB())); device.setDeviceProfileId(deviceProfileId); } + Optional deviceDataOpt = + dataDecodingEncodingService.decode(deviceUpdateMsg.getDeviceDataBytes().toByteArray()); + if (deviceDataOpt.isPresent()) { + device.setDeviceData(deviceDataOpt.get()); + } if (created) { deviceValidator.validate(device, Device::getTenantId); device.setId(deviceId); @@ -244,7 +260,7 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { DeviceCredentials deviceCredentials = new DeviceCredentials(); deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId())); deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); - deviceCredentials.setCredentialsId(org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric(20)); + deviceCredentials.setCredentialsId(StringUtils.randomAlphanumeric(20)); deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials); } createRelationFromEdge(tenantId, edge.getId(), device.getId()); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java index e098d6b605..8de6ec9aac 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java @@ -59,7 +59,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; -import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.thingsboard.server.common.data.StringUtils.isBlank; @Service @AllArgsConstructor diff --git a/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java b/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java index 2cff9841da..2d90b8a16a 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java @@ -20,12 +20,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; -import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.ShortCustomerInfo; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; -import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.dashboard.DashboardService; diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index f193b10a62..38ff5aacec 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -20,10 +20,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.common.data.rule.RuleChain; diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java index c1d41a6f6e..003f544b56 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseUpgradeService.java @@ -16,11 +16,11 @@ package org.thingsboard.server.service.install; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.SystemUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.dao.util.SqlTsDao; import java.io.File; diff --git a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java index 245dc4503d..16c054b2fc 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java @@ -16,12 +16,12 @@ package org.thingsboard.server.service.install; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.SystemUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.dao.util.TimescaleDBTsDao; import java.io.File; diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraTsLatestToSqlMigrateService.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraTsLatestToSqlMigrateService.java index 142bacd7ff..1aa7602a35 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraTsLatestToSqlMigrateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraTsLatestToSqlMigrateService.java @@ -16,12 +16,12 @@ package org.thingsboard.server.service.install.migrate; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.dao.cassandra.CassandraCluster; import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index 1eda4e027d..d02f50b37b 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -37,7 +37,10 @@ import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmSeverity; -import org.thingsboard.server.common.data.id.*; +import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; @@ -46,7 +49,11 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.FilterPredicateValue; -import org.thingsboard.server.common.data.queue.*; +import org.thingsboard.server.common.data.queue.ProcessingStrategy; +import org.thingsboard.server.common.data.queue.ProcessingStrategyType; +import org.thingsboard.server.common.data.queue.Queue; +import org.thingsboard.server.common.data.queue.SubmitStrategy; +import org.thingsboard.server.common.data.queue.SubmitStrategyType; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rule.RuleChain; @@ -74,12 +81,11 @@ import org.thingsboard.server.service.install.TbRuleEngineQueueConfigService; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; -import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.thingsboard.server.common.data.StringUtils.isBlank; @Service @Profile("install") diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/RateLimitsUpdater.java b/application/src/main/java/org/thingsboard/server/service/install/update/RateLimitsUpdater.java index c136f97b52..87179b26b5 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/RateLimitsUpdater.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/RateLimitsUpdater.java @@ -15,10 +15,10 @@ */ package org.thingsboard.server.service.install.update; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index aa8e019317..f433066c06 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -19,10 +19,8 @@ import com.fasterxml.jackson.databind.JsonNode; import freemarker.template.Configuration; import freemarker.template.Template; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Lazy; import org.springframework.core.NestedRuntimeException; @@ -39,6 +37,7 @@ import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageStateMailMessage; import org.thingsboard.server.common.data.ApiUsageStateValue; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; @@ -49,15 +48,12 @@ import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; import javax.mail.internet.MimeMessage; import java.io.ByteArrayInputStream; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Properties; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 062e4128d4..8ec29672de 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -41,7 +41,6 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; @@ -53,7 +52,6 @@ import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; @@ -68,11 +66,11 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.NotificationsTopicService; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.service.gateway_device.GatewayNotificationsService; import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import java.util.HashSet; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; @@ -167,7 +165,7 @@ public class DefaultTbClusterService implements TbClusterService { @Override public void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, TbMsg tbMsg, TbQueueCallback callback) { - if (tenantId.isNullUid()) { + if (tenantId == null || tenantId.isNullUid()) { if (entityId.getEntityType().equals(EntityType.TENANT)) { tenantId = TenantId.fromUUID(entityId.getId()); } else { diff --git a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java index d83508220b..5853516311 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java @@ -16,7 +16,6 @@ package org.thingsboard.server.service.resource; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.eclipse.leshan.core.model.DDFFileParser; import org.eclipse.leshan.core.model.DefaultDDFFileValidator; import org.eclipse.leshan.core.model.InvalidDDFFileException; @@ -24,6 +23,7 @@ import org.eclipse.leshan.core.model.ObjectModel; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.ResourceType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.User; diff --git a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java index 4b2bcb99da..c90e104ff4 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java @@ -22,7 +22,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenProcessingFilter.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenProcessingFilter.java index 28794fc77e..7937641d16 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenProcessingFilter.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenProcessingFilter.java @@ -17,7 +17,6 @@ package org.thingsboard.server.service.security.auth.jwt; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; @@ -26,6 +25,7 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.service.security.auth.RefreshAuthenticationToken; import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException; import org.thingsboard.server.service.security.model.token.RawAccessJwtToken; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java index b29164fb6b..0b90cd8047 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtHeaderTokenExtractor.java @@ -15,9 +15,9 @@ */ package org.thingsboard.server.service.security.auth.jwt.extractor; -import org.apache.commons.lang3.StringUtils; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.config.ThingsboardSecurityConfiguration; import javax.servlet.http.HttpServletRequest; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java index 17ed090807..cb40379c22 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/extractor/JwtQueryTokenExtractor.java @@ -15,9 +15,9 @@ */ package org.thingsboard.server.service.security.auth.jwt.extractor; -import org.apache.commons.lang3.StringUtils; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.config.ThingsboardSecurityConfiguration; import javax.servlet.http.HttpServletRequest; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java index 0972b378eb..0f36273f41 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java @@ -16,23 +16,23 @@ package org.thingsboard.server.service.security.auth.mfa; import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.LockedException; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettings; +import org.thingsboard.server.common.data.security.model.mfa.account.TwoFaAccountConfig; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderConfig; import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager; -import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettings; -import org.thingsboard.server.common.data.security.model.mfa.account.TwoFaAccountConfig; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderConfig; import org.thingsboard.server.service.security.auth.mfa.provider.TwoFaProvider; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.system.SystemSecurityService; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/BackupCodeTwoFaProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/BackupCodeTwoFaProvider.java index bafc1da9f4..ac8d76b52a 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/BackupCodeTwoFaProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/BackupCodeTwoFaProvider.java @@ -15,11 +15,11 @@ */ package org.thingsboard.server.service.security.auth.mfa.provider.impl; -import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.CollectionsUtil; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.security.model.mfa.account.BackupCodeTwoFaAccountConfig; import org.thingsboard.server.common.data.security.model.mfa.provider.BackupCodeTwoFaProviderConfig; @@ -49,7 +49,7 @@ public class BackupCodeTwoFaProvider implements TwoFaProvider generateCodes(int count, int length) { - return Stream.generate(() -> RandomStringUtils.random(length, "0123456789abcdef")) + return Stream.generate(() -> StringUtils.random(length, "0123456789abcdef")) .distinct().limit(count) .collect(Collectors.toSet()); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/OtpBasedTwoFaProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/OtpBasedTwoFaProvider.java index d3f83724cf..b75e81be6f 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/OtpBasedTwoFaProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/OtpBasedTwoFaProvider.java @@ -16,10 +16,10 @@ package org.thingsboard.server.service.security.auth.mfa.provider.impl; import lombok.Data; -import org.apache.commons.lang3.RandomStringUtils; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.thingsboard.server.common.data.CacheConstants; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.security.model.mfa.account.OtpBasedTwoFaAccountConfig; import org.thingsboard.server.common.data.security.model.mfa.provider.OtpBasedTwoFaProviderConfig; @@ -40,7 +40,7 @@ public abstract class OtpBasedTwoFaProvider historyFuture; - if (cmd.getHistoryCmd() != null) { - log.trace("[{}][{}] Going to process history command: {}", session.getSessionId(), cmd.getCmdId(), cmd.getHistoryCmd()); - try { - historyFuture = handleHistoryCmd(ctx, cmd.getHistoryCmd()); - } catch (RuntimeException e) { - handleWsCmdRuntimeException(ctx.getSessionId(), e, cmd); - return; + + try { + List> cmdFutures = new ArrayList<>(); + if (cmd.getAggHistoryCmd() != null) { + cmdFutures.add(handleAggHistoryCmd(ctx, cmd.getAggHistoryCmd())); } - } else { - historyFuture = Futures.immediateFuture(ctx); + if (cmd.getAggTsCmd() != null) { + cmdFutures.add(handleAggTsCmd(ctx, cmd.getAggTsCmd())); + } + if (cmd.getHistoryCmd() != null) { + cmdFutures.add(handleHistoryCmd(ctx, cmd.getHistoryCmd())); + } + if (cmdFutures.isEmpty()) { + handleRegularCommands(ctx, cmd); + } else { + TbEntityDataSubCtx finalCtx = ctx; + Futures.addCallback(Futures.allAsList(cmdFutures), new FutureCallback<>() { + @Override + public void onSuccess(@Nullable List result) { + handleRegularCommands(finalCtx, cmd); + } + + @Override + public void onFailure(Throwable t) { + log.warn("[{}][{}] Failed to process command", finalCtx.getSessionId(), finalCtx.getCmdId()); + } + }, wsCallBackExecutor); + } + } catch (RuntimeException e) { + handleWsCmdRuntimeException(ctx.getSessionId(), e, cmd); + } + } + + private void handleRegularCommands(TbEntityDataSubCtx ctx, EntityDataCmd cmd) { + try { + if (cmd.getLatestCmd() != null || cmd.getTsCmd() != null) { + if (cmd.getLatestCmd() != null) { + handleLatestCmd(ctx, cmd.getLatestCmd()); + } + if (cmd.getTsCmd() != null) { + handleTimeSeriesCmd(ctx, cmd.getTsCmd()); + } + } else { + checkAndSendInitialData(ctx); + } + } catch (RuntimeException e) { + handleWsCmdRuntimeException(ctx.getSessionId(), e, cmd); } - Futures.addCallback(historyFuture, new FutureCallback<>() { - @Override - public void onSuccess(@Nullable TbEntityDataSubCtx theCtx) { + } + + private void checkAndSendInitialData(@Nullable TbEntityDataSubCtx theCtx) { + if (!theCtx.isInitialDataSent()) { + EntityDataUpdate update = new EntityDataUpdate(theCtx.getCmdId(), theCtx.getData(), null, theCtx.getMaxEntitiesPerDataSubscription()); + theCtx.sendWsMsg(update); + theCtx.setInitialDataSent(true); + } + } + + private ListenableFuture handleAggHistoryCmd(TbEntityDataSubCtx ctx, AggHistoryCmd cmd) { + ConcurrentMap queries = new ConcurrentHashMap<>(); + for (AggKey key : cmd.getKeys()) { + if (key.getPreviousValueOnly() == null || !key.getPreviousValueOnly()) { + var query = new BaseReadTsKvQuery(key.getKey(), cmd.getStartTs(), cmd.getEndTs(), cmd.getEndTs() - cmd.getStartTs(), 1, key.getAgg()); + queries.put(query.getId(), new ReadTsKvQueryInfo(key, query, false)); + } + if (key.getPreviousStartTs() != null && key.getPreviousEndTs() != null && key.getPreviousEndTs() >= key.getPreviousStartTs()) { + var query = new BaseReadTsKvQuery(key.getKey(), key.getPreviousStartTs(), key.getPreviousEndTs(), key.getPreviousEndTs() - key.getPreviousStartTs(), 1, key.getAgg()); + queries.put(query.getId(), new ReadTsKvQueryInfo(key, query, true)); + } + } + return handleAggCmd(ctx, cmd.getKeys(), queries, cmd.getStartTs(), cmd.getEndTs(), false); + } + + private ListenableFuture handleAggTsCmd(TbEntityDataSubCtx ctx, AggTimeSeriesCmd cmd) { + ConcurrentMap queries = new ConcurrentHashMap<>(); + for (AggKey key : cmd.getKeys()) { + var query = new BaseReadTsKvQuery(key.getKey(), cmd.getStartTs(), cmd.getStartTs() + cmd.getTimeWindow(), cmd.getTimeWindow(), 1, key.getAgg()); + queries.put(query.getId(), new ReadTsKvQueryInfo(key, query, false)); + } + return handleAggCmd(ctx, cmd.getKeys(), queries, cmd.getStartTs(), cmd.getStartTs() + cmd.getTimeWindow(), true); + } + + private ListenableFuture handleAggCmd(TbEntityDataSubCtx ctx, List keys, ConcurrentMap queries, + long startTs, long endTs, boolean subscribe) { + Map>> fetchResultMap = new HashMap<>(); + List entityDataList = ctx.getData().getData(); + List queryList = queries.values().stream().map(ReadTsKvQueryInfo::getQuery).collect(Collectors.toList()); + entityDataList.forEach(entityData -> fetchResultMap.put(entityData, + tsService.findAllByQueries(ctx.getTenantId(), entityData.getEntityId(), queryList))); + return Futures.transform(Futures.allAsList(fetchResultMap.values()), f -> { + // Map that holds last ts for each key for each entity. + Map> lastTsEntityMap = new HashMap<>(); + fetchResultMap.forEach((entityData, future) -> { try { - if (cmd.getLatestCmd() != null || cmd.getTsCmd() != null) { - if (cmd.getLatestCmd() != null) { - handleLatestCmd(theCtx, cmd.getLatestCmd()); - } - if (cmd.getTsCmd() != null) { - handleTimeSeriesCmd(theCtx, cmd.getTsCmd()); + Map lastTsMap = new HashMap<>(); + lastTsEntityMap.put(entityData, lastTsMap); + + List queryResults = future.get(); + if (queryResults != null) { + for (ReadTsKvQueryResult queryResult : queryResults) { + ReadTsKvQueryInfo queryInfo = queries.get(queryResult.getQueryId()); + ComparisonTsValue comparisonTsValue = entityData.getAggLatest().computeIfAbsent(queryInfo.getKey().getId(), agg -> new ComparisonTsValue()); + if (queryInfo.isPrevious()) { + comparisonTsValue.setPrevious(queryResult.toTsValue()); + } else { + comparisonTsValue.setCurrent(queryResult.toTsValue()); + lastTsMap.put(queryInfo.getQuery().getKey(), queryResult.getLastEntryTs()); + } } - } else if (!theCtx.isInitialDataSent()) { - EntityDataUpdate update = new EntityDataUpdate(theCtx.getCmdId(), theCtx.getData(), null, theCtx.getMaxEntitiesPerDataSubscription()); - theCtx.sendWsMsg(update); - theCtx.setInitialDataSent(true); } - } catch (RuntimeException e) { - handleWsCmdRuntimeException(theCtx.getSessionId(), e, cmd); + // Populate with empty values if no data found. + keys.forEach(key -> { + entityData.getAggLatest().putIfAbsent(key.getId(), new ComparisonTsValue(TsValue.EMPTY, TsValue.EMPTY)); + }); + } catch (InterruptedException | ExecutionException e) { + log.warn("[{}][{}][{}] Failed to fetch historical data", ctx.getSessionId(), ctx.getCmdId(), entityData.getEntityId(), e); + ctx.sendWsMsg(new EntityDataUpdate(ctx.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR.getCode(), "Failed to fetch historical data!")); } + }); + ctx.getWsLock().lock(); + try { + EntityDataUpdate update; + if (!ctx.isInitialDataSent()) { + update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription()); + ctx.setInitialDataSent(true); + } else { + update = new EntityDataUpdate(ctx.getCmdId(), null, entityDataList, ctx.getMaxEntitiesPerDataSubscription()); + } + if (subscribe) { + ctx.createTimeSeriesSubscriptions(lastTsEntityMap, startTs, endTs, true); + } + ctx.sendWsMsg(update); + entityDataList.forEach(ed -> ed.getTimeseries().clear()); + } finally { + ctx.getWsLock().unlock(); } - - @Override - public void onFailure(Throwable t) { - log.warn("[{}][{}] Failed to process command", session.getSessionId(), cmd.getCmdId()); - } + return ctx; }, wsCallBackExecutor); } @@ -416,36 +523,58 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc } private ListenableFuture handleGetTsCmd(TbEntityDataSubCtx ctx, GetTsCmd cmd, boolean subscribe) { + Map queriesKeys = new ConcurrentHashMap<>(); + List keys = cmd.getKeys(); List finalTsKvQueryList; - List tsKvQueryList = cmd.getKeys().stream().map(key -> new BaseReadTsKvQuery( - key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), cmd.getAgg() - )).collect(Collectors.toList()); + List tsKvQueryList = keys.stream().map(key -> { + var query = new BaseReadTsKvQuery( + key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), cmd.getAgg() + ); + queriesKeys.put(query.getId(), query.getKey()); + return query; + }).collect(Collectors.toList()); if (cmd.isFetchLatestPreviousPoint()) { finalTsKvQueryList = new ArrayList<>(tsKvQueryList); - finalTsKvQueryList.addAll(cmd.getKeys().stream().map(key -> new BaseReadTsKvQuery( - key, cmd.getStartTs() - TimeUnit.DAYS.toMillis(365), cmd.getStartTs(), cmd.getInterval(), 1, cmd.getAgg() - )).collect(Collectors.toList())); + finalTsKvQueryList.addAll(keys.stream().map(key -> { + var query = new BaseReadTsKvQuery( + key, cmd.getStartTs() - TimeUnit.DAYS.toMillis(365), cmd.getStartTs(), cmd.getInterval(), 1, cmd.getAgg()); + queriesKeys.put(query.getId(), query.getKey()); + return query; + } + ).collect(Collectors.toList())); } else { finalTsKvQueryList = tsKvQueryList; } - Map>> fetchResultMap = new HashMap<>(); - ctx.getData().getData().forEach(entityData -> fetchResultMap.put(entityData, - tsService.findAll(ctx.getTenantId(), entityData.getEntityId(), finalTsKvQueryList))); + Map>> fetchResultMap = new HashMap<>(); + List entityDataList = ctx.getData().getData(); + entityDataList.forEach(entityData -> fetchResultMap.put(entityData, + tsService.findAllByQueries(ctx.getTenantId(), entityData.getEntityId(), finalTsKvQueryList))); return Futures.transform(Futures.allAsList(fetchResultMap.values()), f -> { + // Map that holds last ts for each key for each entity. + Map> lastTsEntityMap = new HashMap<>(); fetchResultMap.forEach((entityData, future) -> { - Map> keyData = new LinkedHashMap<>(); - cmd.getKeys().forEach(key -> keyData.put(key, new ArrayList<>())); try { - List entityTsData = future.get(); - if (entityTsData != null) { - entityTsData.forEach(entry -> keyData.get(entry.getKey()).add(new TsValue(entry.getTs(), entry.getValueAsString()))); + Map lastTsMap = new HashMap<>(); + lastTsEntityMap.put(entityData, lastTsMap); + + List queryResults = future.get(); + if (queryResults != null) { + for (ReadTsKvQueryResult queryResult : queryResults) { + String queryKey = queriesKeys.get(queryResult.getQueryId()); + entityData.getTimeseries().put(queryKey, queryResult.toTsValues()); + lastTsMap.put(queryKey, queryResult.getLastEntryTs()); + } } - keyData.forEach((k, v) -> entityData.getTimeseries().put(k, v.toArray(new TsValue[v.size()]))); + // Populate with empty values if no data found. + keys.forEach(key -> { + if (!entityData.getTimeseries().containsKey(key)) { + entityData.getTimeseries().put(key, new TsValue[0]); + } + }); + if (cmd.isFetchLatestPreviousPoint()) { - entityData.getTimeseries().values().forEach(dataArray -> { - Arrays.sort(dataArray, (o1, o2) -> Long.compare(o2.getTs(), o1.getTs())); - }); + entityData.getTimeseries().values().forEach(dataArray -> Arrays.sort(dataArray, (o1, o2) -> Long.compare(o2.getTs(), o1.getTs()))); } } catch (InterruptedException | ExecutionException e) { log.warn("[{}][{}][{}] Failed to fetch historical data", ctx.getSessionId(), ctx.getCmdId(), entityData.getEntityId(), e); @@ -459,13 +588,13 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription()); ctx.setInitialDataSent(true); } else { - update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData(), ctx.getMaxEntitiesPerDataSubscription()); + update = new EntityDataUpdate(ctx.getCmdId(), null, entityDataList, ctx.getMaxEntitiesPerDataSubscription()); } if (subscribe) { - ctx.createTimeseriesSubscriptions(keys.stream().map(key -> new EntityKey(EntityKeyType.TIME_SERIES, key)).collect(Collectors.toList()), cmd.getStartTs(), cmd.getEndTs()); + ctx.createTimeSeriesSubscriptions(lastTsEntityMap, cmd.getStartTs(), cmd.getEndTs()); } ctx.sendWsMsg(update); - ctx.getData().getData().forEach(ed -> ed.getTimeseries().clear()); + entityDataList.forEach(ed -> ed.getTimeseries().clear()); } finally { ctx.getWsLock().unlock(); } @@ -533,11 +662,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc ctx.getWsLock().lock(); try { ctx.createLatestValuesSubscriptions(latestCmd.getKeys()); - if (!ctx.isInitialDataSent()) { - EntityDataUpdate update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription()); - ctx.sendWsMsg(update); - ctx.setInitialDataSent(true); - } + checkAndSendInitialData(ctx); } finally { ctx.getWsLock().unlock(); } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/ReadTsKvQueryInfo.java b/application/src/main/java/org/thingsboard/server/service/subscription/ReadTsKvQueryInfo.java new file mode 100644 index 0000000000..28ca2f6729 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/subscription/ReadTsKvQueryInfo.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.subscription; + +import lombok.Data; +import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.service.telemetry.cmd.v2.AggKey; + +@Data +public class ReadTsKvQueryInfo { + + private final AggKey key; + private final ReadTsKvQuery query; + private final boolean previous; + +} diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java index a8283a1e4d..26e97b6f2c 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java @@ -86,7 +86,7 @@ public abstract class TbAbstractDataSubCtx newDataMap = newData.getData().stream().collect(Collectors.toMap(EntityData::getEntityId, Function.identity(), (a,b)-> a)); + Map newDataMap = newData.getData().stream().collect(Collectors.toMap(EntityData::getEntityId, Function.identity(), (a, b) -> a)); if (oldDataMap.size() == newDataMap.size() && oldDataMap.keySet().equals(newDataMap.keySet())) { log.trace("[{}][{}] No updates to entity data found", sessionRef.getSessionId(), cmdId); } else { @@ -122,8 +122,17 @@ public abstract class TbAbstractDataSubCtx keys, long startTs, long endTs) { - createSubscriptions(keys, false, startTs, endTs); + public void createTimeSeriesSubscriptions(Map> entityKeyStates, long startTs, long endTs) { + createTimeSeriesSubscriptions(entityKeyStates, startTs, endTs, false); + } + + public void createTimeSeriesSubscriptions(Map> entityKeyStates, long startTs, long endTs, boolean resultToLatestValues) { + entityKeyStates.forEach((entityData, keyStates) -> { + int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet(); + subToEntityIdMap.put(subIdx, entityData.getEntityId()); + localSubscriptionService.addSubscription( + createTsSub(entityData, subIdx, false, startTs, endTs, keyStates, resultToLatestValues)); + }); } private void createSubscriptions(List keys, boolean latestValues, long startTs, long endTs) { @@ -191,6 +200,14 @@ public abstract class TbAbstractDataSubCtx keyStates) { + return createTsSub(entityData, subIdx, latestValues, startTs, endTs, keyStates, latestValues); + } + + private TbTimeseriesSubscription createTsSub(EntityData entityData, int subIdx, boolean latestValues, long startTs, long endTs, Map keyStates, boolean resultToLatestValues) { log.trace("[{}][{}][{}] Creating time-series subscription for [{}] with keys: {}", serviceId, cmdId, subIdx, entityData.getEntityId(), keyStates); return TbTimeseriesSubscription.builder() .serviceId(serviceId) @@ -198,7 +215,7 @@ public abstract class TbAbstractDataSubCtx sendWsMsg(sessionId, subscriptionUpdate, EntityKeyType.TIME_SERIES, latestValues)) + .updateConsumer((sessionId, subscriptionUpdate) -> sendWsMsg(sessionId, subscriptionUpdate, EntityKeyType.TIME_SERIES, resultToLatestValues)) .allKeys(false) .keyStates(keyStates) .latestValues(latestValues) diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java index becf92771f..43ebfc4afc 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java @@ -20,7 +20,6 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import lombok.Data; import lombok.SneakyThrows; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.context.SecurityContext; @@ -29,6 +28,7 @@ import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.EntityId; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java index 0551d7e114..d9beb1ed33 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java @@ -26,11 +26,11 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; import org.springframework.web.socket.CloseStatus; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; @@ -716,7 +716,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi "Cmd id is negative value!"); sendWsMsg(sessionRef, update); return false; - } else if (cmd.getQuery() == null && cmd.getLatestCmd() == null && cmd.getHistoryCmd() == null && cmd.getTsCmd() == null) { + } else if (cmd.getQuery() == null && !cmd.hasAnyCmd()) { TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Query is empty!"); sendWsMsg(sessionRef, update); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MBootstrapServersConfiguration.java b/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggHistoryCmd.java similarity index 79% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MBootstrapServersConfiguration.java rename to application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggHistoryCmd.java index e5bdec684a..423c55bd0d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MBootstrapServersConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggHistoryCmd.java @@ -13,15 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap; +package org.thingsboard.server.service.telemetry.cmd.v2; import lombok.Data; import java.util.List; @Data -public class LwM2MBootstrapServersConfiguration { +public class AggHistoryCmd { - List bootstrap; + private List keys; + private long startTs; + private long endTs; } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggKey.java b/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggKey.java new file mode 100644 index 0000000000..d567149d01 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggKey.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.telemetry.cmd.v2; + +import lombok.Data; +import org.thingsboard.server.common.data.kv.Aggregation; + +@Data +public class AggKey { + + private int id; + private String key; + private Aggregation agg; + + private Long previousStartTs; + private Long previousEndTs; + private Boolean previousValueOnly; + +} diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggTimeSeriesCmd.java b/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggTimeSeriesCmd.java new file mode 100644 index 0000000000..1f4d88d2c3 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AggTimeSeriesCmd.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.telemetry.cmd.v2; + +import lombok.Data; + +import java.util.List; + +@Data +public class AggTimeSeriesCmd { + + private List keys; + private long startTs; + private long timeWindow; + +} diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataCmd.java b/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataCmd.java index 04335ed9a5..8a187afa7b 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataCmd.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataCmd.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.telemetry.cmd.v2; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import org.thingsboard.server.common.data.query.EntityDataQuery; @@ -30,17 +31,35 @@ public class EntityDataCmd extends DataCmd { private final LatestValueCmd latestCmd; @Getter private final TimeSeriesCmd tsCmd; + @Getter + private final AggHistoryCmd aggHistoryCmd; + @Getter + private final AggTimeSeriesCmd aggTsCmd; + + public EntityDataCmd(int cmdId, EntityDataQuery query, EntityHistoryCmd historyCmd, LatestValueCmd latestCmd, TimeSeriesCmd tsCmd) { + this(cmdId, query, historyCmd, latestCmd, tsCmd, null, null); + } @JsonCreator public EntityDataCmd(@JsonProperty("cmdId") int cmdId, @JsonProperty("query") EntityDataQuery query, @JsonProperty("historyCmd") EntityHistoryCmd historyCmd, @JsonProperty("latestCmd") LatestValueCmd latestCmd, - @JsonProperty("tsCmd") TimeSeriesCmd tsCmd) { + @JsonProperty("tsCmd") TimeSeriesCmd tsCmd, + @JsonProperty("aggHistoryCmd") AggHistoryCmd aggHistoryCmd, + @JsonProperty("aggTsCmd") AggTimeSeriesCmd aggTsCmd) { super(cmdId); this.query = query; this.historyCmd = historyCmd; this.latestCmd = latestCmd; this.tsCmd = tsCmd; + this.aggHistoryCmd = aggHistoryCmd; + this.aggTsCmd = aggTsCmd; } + + @JsonIgnore + public boolean hasAnyCmd() { + return historyCmd != null || latestCmd != null || tsCmd != null || aggHistoryCmd != null || aggTsCmd != null; + } + } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 83ff290476..9edd5d0ed5 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -39,7 +39,7 @@ server: key_password: "${SSL_PEM_KEY_PASSWORD:server_key_password}" # Keystore server credentials keystore: - # Type of the key store + # Type of the key store (JKS or PKCS12) type: "${SSL_KEY_STORE_TYPE:PKCS12}" # Path to the key store that holds the SSL certificate store_file: "${SSL_KEY_STORE:classpath:keystore/keystore.p12}" @@ -148,7 +148,7 @@ ui: # Help parameters help: # Base url for UI help assets - base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.4}" + base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.4.1}" database: ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records @@ -260,7 +260,7 @@ sql: stats_print_interval_ms: "${SQL_EVENTS_BATCH_STATS_PRINT_MS:10000}" batch_threads: "${SQL_EVENTS_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution partition_size: "${SQL_EVENTS_REGULAR_PARTITION_SIZE_HOURS:168}" # Number of hours to partition the events. The current value corresponds to one week. - debug_partition_size: "${SQL_EVENTS_REGULAR_PARTITION_SIZE_HOURS:1}" # Number of hours to partition the debug events. The current value corresponds to one hour. + debug_partition_size: "${SQL_EVENTS_DEBUG_PARTITION_SIZE_HOURS:1}" # Number of hours to partition the debug events. The current value corresponds to one hour. edge_events: batch_size: "${SQL_EDGE_EVENTS_BATCH_SIZE:1000}" batch_max_delay: "${SQL_EDGE_EVENTS_BATCH_MAX_DELAY_MS:100}" @@ -685,7 +685,7 @@ transport: key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:server_key_password}" # Keystore server credentials keystore: - # Type of the key store + # Type of the key store (JKS or PKCS12) type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}" # Path to the key store that holds the SSL certificate store_file: "${MQTT_SSL_KEY_STORE:mqttserver.jks}" @@ -728,7 +728,7 @@ transport: key_password: "${COAP_DTLS_PEM_KEY_PASSWORD:server_key_password}" # Keystore server credentials keystore: - # Type of the key store + # Type of the key store (JKS or PKCS12) type: "${COAP_DTLS_KEY_STORE_TYPE:JKS}" # Path to the key store that holds the SSL certificate store_file: "${COAP_DTLS_KEY_STORE:coapserver.jks}" @@ -770,7 +770,7 @@ transport: key_password: "${LWM2M_SERVER_PEM_KEY_PASSWORD:server_key_password}" # Keystore server credentials keystore: - # Type of the key store + # Type of the key store (JKS or PKCS12) type: "${LWM2M_SERVER_KEY_STORE_TYPE:JKS}" # Path to the key store that holds the SSL certificate store_file: "${LWM2M_SERVER_KEY_STORE:lwm2mserver.jks}" @@ -806,7 +806,7 @@ transport: key_password: "${LWM2M_BS_PEM_KEY_PASSWORD:server_key_password}" # Keystore server credentials keystore: - # Type of the key store + # Type of the key store (JKS or PKCS12) type: "${LWM2M_BS_KEY_STORE_TYPE:JKS}" # Path to the key store that holds the SSL certificate store_file: "${LWM2M_BS_KEY_STORE:lwm2mserver.jks}" @@ -829,7 +829,7 @@ transport: cert_file: "${LWM2M_TRUST_PEM_CERT:lwm2mtruststorechain.pem}" # Keystore with trust certificates keystore: - # Type of the key store + # Type of the key store (JKS or PKCS12) type: "${LWM2M_TRUST_KEY_STORE_TYPE:JKS}" # Path to the key store that holds the X509 certificates store_file: "${LWM2M_TRUST_KEY_STORE:lwm2mtruststorechain.jks}" diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index 75d3a0aa97..09c40f263c 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -26,9 +26,8 @@ import io.jsonwebtoken.Header; import io.jsonwebtoken.Jwt; import io.jsonwebtoken.Jwts; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; -import org.apache.commons.lang3.StringUtils; import org.hamcrest.Matcher; +import org.hibernate.exception.ConstraintViolationException; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -36,6 +35,7 @@ import org.junit.Rule; import org.junit.rules.TestRule; import org.junit.rules.TestWatcher; import org.junit.runner.Description; +import org.mockito.BDDMockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -56,6 +56,7 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; @@ -68,28 +69,33 @@ import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadCo import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; -import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.config.ThingsboardSecurityConfiguration; +import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.service.mail.TestMailService; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest; import org.thingsboard.server.service.security.auth.rest.LoginRequest; import java.io.IOException; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -658,7 +664,11 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { } protected T readResponse(ResultActions result, TypeReference type) throws Exception { - byte[] content = result.andReturn().getResponse().getContentAsByteArray(); + return readResponse(result.andReturn(), type); + } + + protected T readResponse(MvcResult result, TypeReference type) throws Exception { + byte[] content = result.getResponse().getContentAsByteArray(); return mapper.readerFor(type).readValue(content); } @@ -686,8 +696,8 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { edge.setTenantId(tenantId); edge.setName(name); edge.setType(type); - edge.setSecret(RandomStringUtils.randomAlphanumeric(20)); - edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20)); + edge.setSecret(StringUtils.randomAlphanumeric(20)); + edge.setRoutingKey(StringUtils.randomAlphanumeric(20)); return edge; } @@ -701,4 +711,49 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { return Futures.allAsList(futures); } + protected void testEntityDaoWithRelationsOk(EntityId entityIdFrom, EntityId entityTo, String urlDelete) throws Exception { + createEntityRelation(entityIdFrom, entityTo, "TEST_TYPE"); + assertThat(findRelationsByTo(entityTo)).hasSize(1); + + doDelete(urlDelete).andExpect(status().isOk()); + + assertThat(findRelationsByTo(entityTo)).hasSize(0); + } + + protected void testEntityDaoWithRelationsTransactionalException(Dao dao, EntityId entityIdFrom, EntityId entityTo, + String urlDelete) throws Exception { + entityDaoRemoveByIdWithException (dao); + createEntityRelation(entityIdFrom, entityTo, "TEST_TRANSACTIONAL_TYPE"); + assertThat(findRelationsByTo(entityTo)).hasSize(1); + + doDelete(urlDelete) + .andExpect(status().isInternalServerError()); + + assertThat(findRelationsByTo(entityTo)).hasSize(1); + } + + protected void entityDaoRemoveByIdWithException (Dao dao) throws Exception { + BDDMockito.willThrow(new ConstraintViolationException("mock message", new SQLException(), "MOCK_CONSTRAINT")) + .given(dao).removeById(any(), any()); + } + + protected void afterTestEntityDaoRemoveByIdWithException (Dao dao) throws Exception { + BDDMockito.willCallRealMethod().given(dao).removeById(any(), any()); + } + + protected void createEntityRelation(EntityId entityIdFrom, EntityId entityIdTo, String typeRelation) throws Exception { + EntityRelation relation = new EntityRelation(entityIdFrom, entityIdTo, typeRelation); + doPost("/api/relation", relation); + } + + protected List findRelationsByTo(EntityId entityId) throws Exception { + String url = String.format("/api/relations?toId=%s&toType=%s", entityId.getId(), entityId.getEntityType().name()); + MvcResult mvcResult = doGet(url).andReturn(); + + switch (mvcResult.getResponse().getStatus()) { + case 200: return readResponse(mvcResult, new TypeReference<>() {}); + case 404: return Collections.emptyList(); + } + throw new AssertionError("Unexpected status " + mvcResult.getResponse().getStatus()); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java index 14e260ab14..a2b8b908d5 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java @@ -23,6 +23,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; @@ -31,7 +32,9 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.dao.alarm.AlarmDao; import java.util.LinkedList; import java.util.List; @@ -46,6 +49,10 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { protected Device customerDevice; + + @SpyBean + private AlarmDao alarmDao; + @Before public void setup() throws Exception { loginTenantAdmin(); @@ -64,6 +71,9 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { @After public void teardown() throws Exception { loginSysAdmin(); + + afterTestEntityDaoRemoveByIdWithException (alarmDao); + deleteDifferentTenant(); } @@ -421,6 +431,21 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { Assert.assertTrue("Created alarm doesn't match the found one!", equals); } + + @Test + public void testDeleteAlarmWithDeleteRelationsOk() throws Exception { + loginCustomerUser(); + AlarmId alarmId = createAlarm("Alarm for Test WithRelationsOk").getId(); + testEntityDaoWithRelationsOk(customerDevice.getId(), alarmId, "/api/alarm/" + alarmId); + } + + @Test + public void testDeleteAlarmExceptionWithRelationsTransactional() throws Exception { + loginCustomerUser(); + AlarmId alarmId = createAlarm("Alarm for Test WithRelations Transactional Exception").getId(); + testEntityDaoWithRelationsTransactionalException(alarmDao, customerDevice.getId(), alarmId, "/api/alarm/" + alarmId); + } + private Alarm createAlarm(String type) throws Exception { Alarm alarm = Alarm.builder() .tenantId(tenantId) diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java index 0aac87cea0..4e9c01e4cc 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java @@ -17,24 +17,27 @@ package org.thingsboard.server.controller; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.core.type.TypeReference; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.asset.AssetDao; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.service.stats.DefaultRuleEngineStatisticsService; @@ -54,6 +57,9 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { private Tenant savedTenant; private User tenantAdmin; + @SpyBean + private AssetDao assetDao; + @Before public void beforeTest() throws Exception { loginSysAdmin(); @@ -77,6 +83,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { public void afterTest() throws Exception { loginSysAdmin(); + afterTestEntityDaoRemoveByIdWithException (assetDao); + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) .andExpect(status().isOk()); } @@ -117,7 +125,7 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @Test public void testSaveAssetWithViolationOfLengthValidation() throws Exception { Asset asset = new Asset(); - asset.setName(RandomStringUtils.randomAlphabetic(300)); + asset.setName(StringUtils.randomAlphabetic(300)); asset.setType("default"); Mockito.reset(tbClusterService, auditLogService); @@ -132,7 +140,7 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { Mockito.reset(tbClusterService, auditLogService); asset.setName("Normal name"); - asset.setType(RandomStringUtils.randomAlphabetic(300)); + asset.setType(StringUtils.randomAlphabetic(300)); msgError = msgErrorFieldLength("type"); doPost("/api/asset", asset) .andExpect(status().isBadRequest()) @@ -143,7 +151,7 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { Mockito.reset(tbClusterService, auditLogService); asset.setType("default"); - asset.setLabel(RandomStringUtils.randomAlphabetic(300)); + asset.setLabel(StringUtils.randomAlphabetic(300)); msgError = msgErrorFieldLength("label"); doPost("/api/asset", asset) .andExpect(status().isBadRequest()) @@ -471,7 +479,7 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { List assetsTitle1 = new ArrayList<>(); for (int i = 0; i < 143; i++) { Asset asset = new Asset(); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); @@ -482,7 +490,7 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { List assetsTitle2 = new ArrayList<>(); for (int i = 0; i < 75; i++) { Asset asset = new Asset(); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); @@ -557,7 +565,7 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { List assetsType1 = new ArrayList<>(); for (int i = 0; i < 143; i++) { Asset asset = new Asset(); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); @@ -569,7 +577,7 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { List assetsType2 = new ArrayList<>(); for (int i = 0; i < 75; i++) { Asset asset = new Asset(); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); @@ -684,7 +692,7 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { List assetsTitle1 = new ArrayList<>(); for (int i = 0; i < 125; i++) { Asset asset = new Asset(); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); @@ -697,7 +705,7 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { List assetsTitle2 = new ArrayList<>(); for (int i = 0; i < 143; i++) { Asset asset = new Asset(); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); @@ -779,7 +787,7 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { List assetsType1 = new ArrayList<>(); for (int i = 0; i < 125; i++) { Asset asset = new Asset(); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); @@ -793,7 +801,7 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { List assetsType2 = new ArrayList<>(); for (int i = 0; i < 143; i++) { Asset asset = new Asset(); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); @@ -905,4 +913,23 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { Assert.assertEquals(0, pageData.getData().size()); } + + @Test + public void testDeleteAssetWithDeleteRelationsOk() throws Exception { + AssetId assetId = createAsset("Asset for Test WithRelationsOk").getId(); + testEntityDaoWithRelationsOk(savedTenant.getId(), assetId, "/api/asset/" + assetId); + } + + @Test + public void testDeleteAssetExceptionWithRelationsTransactional() throws Exception { + AssetId assetId = createAsset("Asset for Test WithRelations Transactional Exception").getId(); + testEntityDaoWithRelationsTransactionalException(assetDao, savedTenant.getId(), assetId, "/api/asset/" + assetId); + } + + private Asset createAsset(String name) { + Asset asset = new Asset(); + asset.setName(name); + asset.setType("default"); + return doPost("/api/asset", asset, Asset.class); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java index bd0a416ccf..d3cc1761c7 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java @@ -20,14 +20,15 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; @@ -36,6 +37,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; @@ -55,6 +57,9 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest private Tenant savedTenant; private User tenantAdmin; + @SpyBean + private CustomerDao customerDao; + @Before public void beforeTest() throws Exception { executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass())); @@ -82,6 +87,8 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest loginSysAdmin(); + afterTestEntityDaoRemoveByIdWithException (customerDao); + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) .andExpect(status().isOk()); } @@ -122,7 +129,7 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest @Test public void testSaveCustomerWithViolationOfValidation() throws Exception { Customer customer = new Customer(); - customer.setTitle(RandomStringUtils.randomAlphabetic(300)); + customer.setTitle(StringUtils.randomAlphabetic(300)); Mockito.reset(tbClusterService, auditLogService); @@ -137,7 +144,7 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest Mockito.reset(tbClusterService, auditLogService); customer.setTitle("Normal title"); - customer.setCity(RandomStringUtils.randomAlphabetic(300)); + customer.setCity(StringUtils.randomAlphabetic(300)); msgError = msgErrorFieldLength("city"); doPost("/api/customer", customer) .andExpect(status().isBadRequest()) @@ -148,7 +155,7 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest Mockito.reset(tbClusterService, auditLogService); customer.setCity("Normal city"); - customer.setCountry(RandomStringUtils.randomAlphabetic(300)); + customer.setCountry(StringUtils.randomAlphabetic(300)); msgError = msgErrorFieldLength("country"); doPost("/api/customer", customer) .andExpect(status().isBadRequest()) @@ -159,7 +166,7 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest Mockito.reset(tbClusterService, auditLogService); customer.setCountry("Ukraine"); - customer.setPhone(RandomStringUtils.randomAlphabetic(300)); + customer.setPhone(StringUtils.randomAlphabetic(300)); msgError = msgErrorFieldLength("phone"); doPost("/api/customer", customer) .andExpect(status().isBadRequest()) @@ -170,7 +177,7 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest Mockito.reset(tbClusterService, auditLogService); customer.setPhone("+3892555554512"); - customer.setState(RandomStringUtils.randomAlphabetic(300)); + customer.setState(StringUtils.randomAlphabetic(300)); msgError = msgErrorFieldLength("state"); doPost("/api/customer", customer) .andExpect(status().isBadRequest()) @@ -181,7 +188,7 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest Mockito.reset(tbClusterService, auditLogService); customer.setState("Normal state"); - customer.setZip(RandomStringUtils.randomAlphabetic(300)); + customer.setZip(StringUtils.randomAlphabetic(300)); msgError = msgErrorFieldLength("zip or postal code"); doPost("/api/customer", customer) .andExpect(status().isBadRequest()) @@ -339,7 +346,7 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest for (int i = 0; i < 143; i++) { Customer customer = new Customer(); customer.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); + String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); String title = title1 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); customer.setTitle(title); @@ -353,7 +360,7 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest for (int i = 0; i < 175; i++) { Customer customer = new Customer(); customer.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); + String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); String title = title2 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); customer.setTitle(title); @@ -402,4 +409,22 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } + + @Test + public void testDeleteCustomerWithDeleteRelationsOk() throws Exception { + CustomerId customerId = createCustomer("Customer for Test WithRelationsOk").getId(); + testEntityDaoWithRelationsOk(savedTenant.getId(), customerId, "/api/customer/" + customerId); + } + + @Test + public void testDeleteCustomerExceptionWithRelationsTransactional() throws Exception { + CustomerId customerId = createCustomer("Customer for Test WithRelations Transactional Exception").getId(); + testEntityDaoWithRelationsTransactionalException(customerDao, savedTenant.getId(), customerId, "/api/customer/" + customerId); + } + + private Customer createCustomer(String title) { + Customer customer = new Customer(); + customer.setTitle(title); + return doPost("/api/customer", customer, Customer.class); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java index 66fc1a5178..50050c6306 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java @@ -17,23 +17,26 @@ package org.thingsboard.server.controller; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.core.type.TypeReference; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DashboardInfo; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.dashboard.DashboardDao; import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; @@ -50,6 +53,9 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest private Tenant savedTenant; private User tenantAdmin; + @SpyBean + private DashboardDao dashboardDao; + @Before public void beforeTest() throws Exception { loginSysAdmin(); @@ -73,45 +79,16 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest public void afterTest() throws Exception { loginSysAdmin(); + afterTestEntityDaoRemoveByIdWithException (dashboardDao); + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) .andExpect(status().isOk()); } - @Test - public void testSaveDashboard() throws Exception { - Dashboard dashboard = new Dashboard(); - dashboard.setTitle("My dashboard"); - - Mockito.reset(tbClusterService, auditLogService); - - Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); - - testNotifyEntityOneTimeMsgToEdgeServiceNever(savedDashboard, savedDashboard.getId(), savedDashboard.getId(), savedTenant.getId(), - tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED); - - Assert.assertNotNull(savedDashboard); - Assert.assertNotNull(savedDashboard.getId()); - Assert.assertTrue(savedDashboard.getCreatedTime() > 0); - Assert.assertEquals(savedTenant.getId(), savedDashboard.getTenantId()); - Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle()); - - savedDashboard.setTitle("My new dashboard"); - - Mockito.reset(tbClusterService, auditLogService); - - doPost("/api/dashboard", savedDashboard, Dashboard.class); - - testNotifyEntityAllOneTime(savedDashboard, savedDashboard.getId(), savedDashboard.getId(), savedTenant.getId(), - tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED); - - Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); - Assert.assertEquals(foundDashboard.getTitle(), savedDashboard.getTitle()); - } - @Test public void testSaveDashboardInfoWithViolationOfValidation() throws Exception { Dashboard dashboard = new Dashboard(); - dashboard.setTitle(RandomStringUtils.randomAlphabetic(300)); + dashboard.setTitle(StringUtils.randomAlphabetic(300)); String msgError = msgErrorFieldLength("title"); Mockito.reset(tbClusterService, auditLogService); @@ -335,7 +312,7 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest int cntEntity = 134; for (int i = 0; i < cntEntity; i++) { Dashboard dashboard = new Dashboard(); - String suffix = RandomStringUtils.randomAlphanumeric((int) (Math.random() * 15)); + String suffix = StringUtils.randomAlphanumeric((int) (Math.random() * 15)); String title = title1 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); dashboard.setTitle(title); @@ -346,7 +323,7 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest for (int i = 0; i < 112; i++) { Dashboard dashboard = new Dashboard(); - String suffix = RandomStringUtils.randomAlphanumeric((int) (Math.random() * 15)); + String suffix = StringUtils.randomAlphanumeric((int) (Math.random() * 15)); String title = title2 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); dashboard.setTitle(title); @@ -495,4 +472,21 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest Assert.assertEquals(0, pageData.getData().size()); } + @Test + public void testDeleteDashboardWithDeleteRelationsOk() throws Exception { + DashboardId dashboardId = createDashboard("Dashboard for Test WithRelationsOk").getId(); + testEntityDaoWithRelationsOk(savedTenant.getId(), dashboardId, "/api/dashboard/" + dashboardId); + } + + @Test + public void testDeleteDashboardExceptionWithRelationsTransactional() throws Exception { + DashboardId dashboardId = createDashboard("Dashboard for Test WithRelations Transactional Exception").getId(); + testEntityDaoWithRelationsTransactionalException(dashboardDao, savedTenant.getId(), dashboardId, "/api/dashboard/" + dashboardId); + } + + private Dashboard createDashboard(String title) { + Dashboard dashboard = new Dashboard(); + dashboard.setTitle(title); + return doPost("/api/dashboard", dashboard, Dashboard.class); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java index ee4d005233..abe8b78918 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java @@ -21,7 +21,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -35,6 +34,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; @@ -50,6 +50,7 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.dao.device.DeviceDao; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.dao.model.ModelConstants; @@ -82,6 +83,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @SpyBean private GatewayNotificationsService gatewayNotificationsService; + @SpyBean + private DeviceDao deviceDao; + + @Before public void beforeTest() throws Exception { executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass())); @@ -109,6 +114,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { loginSysAdmin(); + afterTestEntityDaoRemoveByIdWithException (deviceDao); + doDelete("/api/tenant/" + savedTenant.getId().getId()) .andExpect(status().isOk()); } @@ -164,7 +171,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @Test public void saveDeviceWithViolationOfValidation() throws Exception { Device device = new Device(); - device.setName(RandomStringUtils.randomAlphabetic(300)); + device.setName(StringUtils.randomAlphabetic(300)); device.setType("default"); Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); @@ -181,7 +188,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { device.setTenantId(savedTenant.getId()); msgError = msgErrorFieldLength("type"); - device.setType(RandomStringUtils.randomAlphabetic(300)); + device.setType(StringUtils.randomAlphabetic(300)); doPost("/api/device", device) .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); @@ -193,7 +200,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { msgError = msgErrorFieldLength("label"); device.setType("Normal type"); - device.setLabel(RandomStringUtils.randomAlphabetic(300)); + device.setLabel(StringUtils.randomAlphabetic(300)); doPost("/api/device", device) .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); @@ -709,7 +716,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { futures = new ArrayList<>(143); for (int i = 0; i < 143; i++) { Device device = new Device(); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); @@ -723,7 +730,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { futures = new ArrayList<>(75); for (int i = 0; i < 75; i++) { Device device = new Device(); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); @@ -783,7 +790,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { futures = new ArrayList<>(143); for (int i = 0; i < 143; i++) { Device device = new Device(); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); @@ -801,7 +808,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { futures = new ArrayList<>(75); for (int i = 0; i < 75; i++) { Device device = new Device(); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); @@ -920,7 +927,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { futures = new ArrayList<>(125); for (int i = 0; i < 125; i++) { Device device = new Device(); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); @@ -936,7 +943,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { futures = new ArrayList<>(143); for (int i = 0; i < 143; i++) { Device device = new Device(); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); @@ -1003,7 +1010,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { futures = new ArrayList<>(125); for (int i = 0; i < 125; i++) { Device device = new Device(); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); @@ -1023,7 +1030,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { futures = new ArrayList<>(143); for (int i = 0; i < 143; i++) { Device device = new Device(); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); @@ -1203,4 +1210,23 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { protected void testNotificationDeleteGatewayNever() { Mockito.verify(gatewayNotificationsService, never()).onDeviceDeleted(Mockito.any(Device.class)); } + + @Test + public void testDeleteDashboardWithDeleteRelationsOk() throws Exception { + DeviceId deviceId = createDevice("Device for Test WithRelationsOk").getId(); + testEntityDaoWithRelationsOk(savedTenant.getId(), deviceId, "/api/device/" + deviceId); + } + + @Test + public void testDeleteDeviceExceptionWithRelationsTransactional() throws Exception { + DeviceId deviceId = createDevice("Device for Test WithRelations Transactional Exception").getId(); + testEntityDaoWithRelationsTransactionalException(deviceDao, savedTenant.getId(), deviceId, "/api/device/" + deviceId); + } + + private Device createDevice(String name) { + Device device = new Device(); + device.setName(name); + device.setType("default"); + return doPost("/api/device", device, Device.class); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java index 4f782b15b3..0c042d5006 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java @@ -22,12 +22,12 @@ import com.google.protobuf.DynamicMessage; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.util.JsonFormat; import com.squareup.wire.schema.internal.parser.ProtoFileElement; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Device; @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; @@ -46,10 +47,12 @@ import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadCon import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.device.DeviceProfileDao; import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; @@ -74,6 +77,9 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController private Tenant savedTenant; private User tenantAdmin; + @SpyBean + private DeviceProfileDao deviceProfileDao; + @Before public void beforeTest() throws Exception { loginSysAdmin(); @@ -97,6 +103,8 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController public void afterTest() throws Exception { loginSysAdmin(); + afterTestEntityDaoRemoveByIdWithException (deviceProfileDao); + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) .andExpect(status().isOk()); } @@ -138,7 +146,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController Mockito.reset(tbClusterService, auditLogService); - DeviceProfile createDeviceProfile = this.createDeviceProfile(RandomStringUtils.randomAlphabetic(300)); + DeviceProfile createDeviceProfile = this.createDeviceProfile(StringUtils.randomAlphabetic(300)); doPost("/api/deviceProfile", createDeviceProfile) .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); @@ -1125,4 +1133,20 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController return JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); } + @Test + public void testDeleteDeviceProfileWithDeleteRelationsOk() throws Exception { + DeviceProfileId deviceProfileId = savedDeviceProfile("DeviceProfile for Test WithRelationsOk").getId(); + testEntityDaoWithRelationsOk(savedTenant.getId(), deviceProfileId, "/api/deviceProfile/" + deviceProfileId); + } + + @Test + public void testDeleteDeviceProfileExceptionWithRelationsTransactional() throws Exception { + DeviceProfileId deviceProfileId = savedDeviceProfile("DeviceProfile for Test WithRelations Transactional Exception").getId(); + testEntityDaoWithRelationsTransactionalException(deviceProfileDao, savedTenant.getId(), deviceProfileId, "/api/deviceProfile/" + deviceProfileId); + } + + private DeviceProfile savedDeviceProfile(String name) { + DeviceProfile deviceProfile = createDeviceProfile(name); + return doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java index 8bf763c637..c3d747cb35 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java @@ -17,26 +17,29 @@ package org.thingsboard.server.controller; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.core.type.TypeReference; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.test.context.TestPropertySource; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntitySubtype; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.edge.EdgeDao; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.edge.imitator.EdgeImitator; @@ -72,6 +75,9 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { private TenantId tenantId; private User tenantAdmin; + @SpyBean + private EdgeDao edgeDao; + @Before public void beforeTest() throws Exception { loginSysAdmin(); @@ -96,6 +102,8 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { public void afterTest() throws Exception { loginSysAdmin(); + afterTestEntityDaoRemoveByIdWithException (edgeDao); + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) .andExpect(status().isOk()); } @@ -133,7 +141,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { @Test public void testSaveEdgeWithViolationOfLengthValidation() throws Exception { - Edge edge = constructEdge(RandomStringUtils.randomAlphabetic(300), "default"); + Edge edge = constructEdge(StringUtils.randomAlphabetic(300), "default"); String msgError = msgErrorFieldLength("name"); Mockito.reset(tbClusterService, auditLogService); @@ -148,7 +156,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { msgError = msgErrorFieldLength("type"); edge.setName("normal name"); - edge.setType(RandomStringUtils.randomAlphabetic(300)); + edge.setType(StringUtils.randomAlphabetic(300)); doPost("/api/edge", edge) .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); @@ -159,7 +167,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { msgError = msgErrorFieldLength("label"); edge.setType("normal type"); - edge.setLabel(RandomStringUtils.randomAlphabetic(300)); + edge.setLabel(StringUtils.randomAlphabetic(300)); doPost("/api/edge", edge) .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); @@ -389,7 +397,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { String title1 = "Edge title 1"; List edgesTitle1 = new ArrayList<>(); for (int i = 0; i < 143; i++) { - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, "default"); @@ -398,7 +406,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { String title2 = "Edge title 2"; List edgesTitle2 = new ArrayList<>(); for (int i = 0; i < 75; i++) { - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, "default"); @@ -471,7 +479,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { String type1 = "typeA"; List edgesType1 = new ArrayList<>(); for (int i = 0; i < 143; i++) { - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, type1); @@ -481,7 +489,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { String type2 = "typeB"; List edgesType2 = new ArrayList<>(); for (int i = 0; i < 75; i++) { - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, type2); @@ -600,7 +608,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { String title1 = "Edge title 1"; List edgesTitle1 = new ArrayList<>(); for (int i = 0; i < 125; i++) { - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, "default"); @@ -611,7 +619,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { String title2 = "Edge title 2"; List edgesTitle2 = new ArrayList<>(); for (int i = 0; i < 143; i++) { - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, "default"); @@ -698,7 +706,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { String type1 = "typeC"; List edgesType1 = new ArrayList<>(); for (int i = 0; i < 125; i++) { - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, type1); @@ -710,7 +718,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { String type2 = "typeD"; List edgesType2 = new ArrayList<>(); for (int i = 0; i < 143; i++) { - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, type2); @@ -837,4 +845,20 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } + @Test + public void testDeleteEdgeWithDeleteRelationsOk() throws Exception { + EdgeId edgeId = savedEdge("Edge for Test WithRelationsOk").getId(); + testEntityDaoWithRelationsOk(savedTenant.getId(), edgeId, "/api/edge/" + edgeId); + } + + @Test + public void testDeleteEdgeExceptionWithRelationsTransactional() throws Exception { + EdgeId edgeId = savedEdge("Edge for Test WithRelations Transactional Exception").getId(); + testEntityDaoWithRelationsTransactionalException(edgeDao, savedTenant.getId(), edgeId, "/api/edge/" + edgeId); + } + + private Edge savedEdge(String name) { + Edge edge = constructEdge(name, "default"); + return doPost("/api/edge", edge, Edge.class); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java index f1121dfb14..5bddb9b3a7 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java @@ -22,7 +22,6 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; @@ -33,6 +32,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.ResultActions; import org.thingsboard.common.util.ThingsBoardExecutors; @@ -40,11 +40,13 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityViewInfo; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.objects.AttributesEntityView; import org.thingsboard.server.common.data.objects.TelemetryEntityView; import org.thingsboard.server.common.data.page.PageData; @@ -54,6 +56,7 @@ import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.dao.entityview.EntityViewDao; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; @@ -93,6 +96,9 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes List> deleteFutures = new ArrayList<>(); ListeningExecutorService executor; + @SpyBean + private EntityViewDao entityViewDao; + @Before public void beforeTest() throws Exception { executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass())); @@ -114,6 +120,9 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @After public void afterTest() throws Exception { + + afterTestEntityDaoRemoveByIdWithException (entityViewDao); + executor.shutdownNow(); } @@ -170,7 +179,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @Test public void testSaveEntityViewWithViolationOfValidation() throws Exception { - EntityView entityView = createEntityView(RandomStringUtils.randomAlphabetic(300), 0, 0); + EntityView entityView = createEntityView(StringUtils.randomAlphabetic(300), 0, 0); Mockito.reset(tbClusterService, auditLogService); @@ -186,7 +195,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes entityView.setName("Normal name"); msgError = msgErrorFieldLength("type"); - entityView.setType(RandomStringUtils.randomAlphabetic(300)); + entityView.setType(StringUtils.randomAlphabetic(300)); doPost("/api/entityView", entityView) .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); @@ -722,7 +731,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes }); viewNameFutures.add(Futures.transform(customerFuture, customerId -> { - String fullName = partOfName + ' ' + RandomStringUtils.randomAlphanumeric(15); + String fullName = partOfName + ' ' + StringUtils.randomAlphanumeric(15); fullName = even ? fullName.toLowerCase() : fullName.toUpperCase(); EntityView view = getNewSavedEntityView(fullName); view.setCustomerId(customerId); @@ -786,4 +795,16 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes Assert.assertEquals(0, pageData.getData().size()); } + + @Test + public void testDeleteEntityViewWithDeleteRelationsOk() throws Exception { + EntityViewId entityViewId = getNewSavedEntityView("EntityView for Test WithRelationsOk").getId(); + testEntityDaoWithRelationsOk(tenantId, entityViewId, "/api/entityView/" + entityViewId); + } + + @Test + public void testDeleteEntityViewExceptionWithRelationsTransactional() throws Exception { + EntityViewId entityViewId = getNewSavedEntityView("EntityView for Test WithRelations Transactional Exception").getId(); + testEntityDaoWithRelationsTransactionalException(entityViewDao, tenantId, entityViewId, "/api/entityView/" + entityViewId); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java index 18d76d96e8..c256a6b845 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -30,6 +29,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; @@ -137,7 +137,7 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest(); firmwareInfo.setDeviceProfileId(deviceProfileId); firmwareInfo.setType(FIRMWARE); - firmwareInfo.setTitle(RandomStringUtils.randomAlphabetic(300)); + firmwareInfo.setTitle(StringUtils.randomAlphabetic(300)); firmwareInfo.setVersion(VERSION); firmwareInfo.setUsesUrl(false); String msgError = msgErrorFieldLength("title"); @@ -154,7 +154,7 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes ActionType.ADDED, new DataValidationException(msgError)); firmwareInfo.setTitle(TITLE); - firmwareInfo.setVersion(RandomStringUtils.randomAlphabetic(300)); + firmwareInfo.setVersion(StringUtils.randomAlphabetic(300)); msgError = msgErrorFieldLength("version"); doPost("/api/otaPackage", firmwareInfo) .andExpect(status().isBadRequest()) @@ -168,7 +168,7 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes firmwareInfo.setVersion(VERSION); firmwareInfo.setUsesUrl(true); msgError = msgErrorFieldLength("url"); - firmwareInfo.setUrl(RandomStringUtils.randomAlphabetic(300)); + firmwareInfo.setUrl(StringUtils.randomAlphabetic(300)); doPost("/api/otaPackage", firmwareInfo) .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java index 33602bbfa9..bec15dac8c 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java @@ -16,22 +16,25 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.rule.RuleChainDao; import java.util.ArrayList; import java.util.Collections; @@ -47,6 +50,9 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest private Tenant savedTenant; private User tenantAdmin; + @SpyBean + private RuleChainDao ruleChainDao; + @Before public void beforeTest() throws Exception { loginSysAdmin(); @@ -70,6 +76,8 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest public void afterTest() throws Exception { loginSysAdmin(); + afterTestEntityDaoRemoveByIdWithException(ruleChainDao); + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) .andExpect(status().isOk()); } @@ -107,7 +115,7 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest Mockito.reset(tbClusterService, auditLogService); RuleChain ruleChain = new RuleChain(); - ruleChain.setName(RandomStringUtils.randomAlphabetic(300)); + ruleChain.setName(StringUtils.randomAlphabetic(300)); String msgError = msgErrorFieldLength("name"); doPost("/api/ruleChain", ruleChain) .andExpect(status().isBadRequest()) @@ -223,4 +231,21 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest Assert.assertEquals(1, pageData.getTotalElements()); } + @Test + public void testDeleteRuleChainWithDeleteRelationsOk() throws Exception { + RuleChainId ruleChainId = createRuleChain("RuleChain for Test WithRelationsOk").getId(); + testEntityDaoWithRelationsOk(savedTenant.getId(), ruleChainId, "/api/ruleChain/" + ruleChainId); + } + + @Test + public void testDeleteRuleChainExceptionWithRelationsTransactional() throws Exception { + RuleChainId ruleChainId = createRuleChain("RuleChain for Test WithRelations Transactional Exception").getId(); + testEntityDaoWithRelationsTransactionalException(ruleChainDao, savedTenant.getId(), ruleChainId, "/api/ruleChain/" + ruleChainId); + } + + private RuleChain createRuleChain(String name) { + RuleChain ruleChain = new RuleChain(); + ruleChain.setName(name); + return doPost("/api/ruleChain", ruleChain, RuleChain.class); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseTbResourceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseTbResourceControllerTest.java index d3153f57bc..cd9ea6f357 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseTbResourceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseTbResourceControllerTest.java @@ -16,13 +16,13 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.thingsboard.server.common.data.ResourceType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.Tenant; @@ -118,7 +118,7 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes public void saveResourceInfoWithViolationOfLengthValidation() throws Exception { TbResource resource = new TbResource(); resource.setResourceType(ResourceType.JKS); - resource.setTitle(RandomStringUtils.randomAlphabetic(300)); + resource.setTitle(StringUtils.randomAlphabetic(300)); resource.setFileName(DEFAULT_FILE_NAME); resource.setData("Test Data"); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java index 1840f1f8a0..4cba7c1ee0 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java @@ -21,7 +21,6 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -31,6 +30,7 @@ import org.mockito.Mockito; import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.ResultActions; import org.thingsboard.common.util.ThingsBoardExecutors; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantInfo; import org.thingsboard.server.common.data.TenantProfile; @@ -120,7 +120,7 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { public void testSaveTenantWithViolationOfValidation() throws Exception { loginSysAdmin(); Tenant tenant = new Tenant(); - tenant.setTitle(RandomStringUtils.randomAlphanumeric(300)); + tenant.setTitle(StringUtils.randomAlphanumeric(300)); Mockito.reset(tbClusterService); @@ -259,7 +259,7 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { List> createFutures = new ArrayList<>(134); for (int i = 0; i < 134; i++) { Tenant tenant = new Tenant(); - String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); + String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); String title = title1 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); tenant.setTitle(title); @@ -274,7 +274,7 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { createFutures = new ArrayList<>(127); for (int i = 0; i < 127; i++) { Tenant tenant = new Tenant(); - String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); + String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); String title = title2 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); tenant.setTitle(title); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java index 11174618ed..0a4f7ac60b 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java @@ -16,12 +16,12 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.Assert; import org.junit.Test; import org.mockito.ArgumentMatcher; import org.mockito.Mockito; import org.thingsboard.server.common.data.EntityInfo; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantProfileId; @@ -84,7 +84,7 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController Mockito.reset(tbClusterService); - TenantProfile tenantProfile = this.createTenantProfile(RandomStringUtils.randomAlphabetic(300)); + TenantProfile tenantProfile = this.createTenantProfile(StringUtils.randomAlphabetic(300)); doPost("/api/tenantProfile", tenantProfile) .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgErrorFieldLength("name")))); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java index 6e8c15cc0e..141896ed4b 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java @@ -18,21 +18,25 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.lang3.RandomStringUtils; +import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.http.HttpHeaders; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.user.UserDao; import org.thingsboard.server.service.mail.TestMailService; import java.util.ArrayList; @@ -53,6 +57,16 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { private CustomerId customerNUULId = (CustomerId) createEntityId_NULL_UUID(new Customer()); + @SpyBean + private UserDao userDao; + + @After + public void afterTest() throws Exception { + loginSysAdmin(); + + afterTestEntityDaoRemoveByIdWithException(userDao); + } + @Test public void testSaveUser() throws Exception { loginSysAdmin(); @@ -131,7 +145,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { user.setAuthority(Authority.TENANT_ADMIN); user.setTenantId(tenantId); user.setEmail(email); - user.setFirstName(RandomStringUtils.randomAlphabetic(300)); + user.setFirstName(StringUtils.randomAlphabetic(300)); user.setLastName("Downs"); String msgError = msgErrorFieldLength("first name"); doPost("/api/user", user) @@ -145,7 +159,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { user.setFirstName("Normal name"); msgError = msgErrorFieldLength("last name"); - user.setLastName(RandomStringUtils.randomAlphabetic(300)); + user.setLastName(StringUtils.randomAlphabetic(300)); doPost("/api/user", user) .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); @@ -440,7 +454,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { User user = new User(); user.setAuthority(Authority.TENANT_ADMIN); user.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); + String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); String email = email1 + suffix + "@thingsboard.org"; email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); user.setEmail(email); @@ -454,7 +468,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { User user = new User(); user.setAuthority(Authority.TENANT_ADMIN); user.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); + String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); String email = email2 + suffix + "@thingsboard.org"; email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); user.setEmail(email); @@ -605,7 +619,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { User user = new User(); user.setAuthority(Authority.CUSTOMER_USER); user.setCustomerId(customerId); - String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); + String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); String email = email1 + suffix + "@thingsboard.org"; email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); user.setEmail(email); @@ -619,7 +633,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { User user = new User(); user.setAuthority(Authority.CUSTOMER_USER); user.setCustomerId(customerId); - String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); + String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); String email = email2 + suffix + "@thingsboard.org"; email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); user.setEmail(email); @@ -688,4 +702,29 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { doDelete("/api/customer/" + customerId.getId().toString()) .andExpect(status().isOk()); } + + + @Test + public void testDeleteUserWithDeleteRelationsOk() throws Exception { + UserId userId = createUser().getId(); + testEntityDaoWithRelationsOk(tenantId, userId, "/api/user/" + userId); + } + + @Test + public void testDeleteUserExceptionWithRelationsTransactional() throws Exception { + UserId userId = createUser().getId(); + testEntityDaoWithRelationsTransactionalException(userDao, tenantId, userId, "/api/user/" + userId); + } + + private User createUser() throws Exception { + loginSysAdmin(); + String email = "tenant2@thingsboard.org"; + User user = new User(); + user.setAuthority(Authority.TENANT_ADMIN); + user.setTenantId(tenantId); + user.setEmail(email); + user.setFirstName("Joe"); + user.setLastName("Downs"); + return doPost("/api/user", user, User.class); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java index 68ed3636e9..5794c08633 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java @@ -16,13 +16,13 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; @@ -109,7 +109,7 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController @Test public void testSaveWidgetBundleWithViolationOfLengthValidation() throws Exception { WidgetsBundle widgetsBundle = new WidgetsBundle(); - widgetsBundle.setTitle(RandomStringUtils.randomAlphabetic(300)); + widgetsBundle.setTitle(StringUtils.randomAlphabetic(300)); Mockito.reset(tbClusterService); diff --git a/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java b/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java index 9c30ebbbd0..9839843b0c 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java @@ -17,8 +17,6 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; -import org.apache.commons.lang3.RandomStringUtils; -import org.apache.commons.lang3.StringUtils; import org.jboss.aerogear.security.otp.Totp; import org.junit.After; import org.junit.Before; @@ -28,6 +26,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.SpyBean; import org.thingsboard.rule.engine.api.SmsService; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionStatus; import org.thingsboard.server.common.data.audit.ActionType; @@ -178,7 +177,7 @@ public abstract class TwoFactorAuthTest extends AbstractControllerTest { logInWithPreVerificationToken(username, password); - Stream.generate(() -> RandomStringUtils.randomNumeric(6)) + Stream.generate(() -> StringUtils.randomNumeric(6)) .limit(9) .forEach(incorrectVerificationCode -> { try { @@ -190,11 +189,11 @@ public abstract class TwoFactorAuthTest extends AbstractControllerTest { } }); - String errorMessage = getErrorMessage(doPost("/api/auth/2fa/verification/check?providerType=TOTP&verificationCode=" + RandomStringUtils.randomNumeric(6)) + String errorMessage = getErrorMessage(doPost("/api/auth/2fa/verification/check?providerType=TOTP&verificationCode=" + StringUtils.randomNumeric(6)) .andExpect(status().isUnauthorized())); assertThat(errorMessage).containsIgnoringCase("account was locked due to exceeded 2fa verification attempts"); - errorMessage = getErrorMessage(doPost("/api/auth/2fa/verification/check?providerType=TOTP&verificationCode=" + RandomStringUtils.randomNumeric(6)) + errorMessage = getErrorMessage(doPost("/api/auth/2fa/verification/check?providerType=TOTP&verificationCode=" + StringUtils.randomNumeric(6)) .andExpect(status().isUnauthorized())); assertThat(errorMessage).containsIgnoringCase("user is disabled"); } diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java index 2cc217e0c1..dcd354ace5 100644 --- a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java @@ -26,7 +26,6 @@ import com.google.protobuf.AbstractMessage; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.MessageLite; -import org.apache.commons.lang3.RandomStringUtils; import org.awaitility.Awaitility; import org.junit.After; import org.junit.Assert; @@ -44,10 +43,12 @@ import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.User; @@ -56,17 +57,32 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; +import org.thingsboard.server.common.data.device.data.DeviceData; +import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration; +import org.thingsboard.server.common.data.device.data.PowerMode; +import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; import org.thingsboard.server.common.data.device.profile.AlarmCondition; import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter; import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey; import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; import org.thingsboard.server.common.data.device.profile.AlarmRule; import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec; +import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.NoSecLwM2MBootstrapServerCredential; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; @@ -77,6 +93,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.page.PageData; @@ -99,6 +116,9 @@ import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; +import org.thingsboard.server.common.data.transport.snmp.config.impl.TelemetryQueryingSnmpCommunicationConfig; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.common.msg.queue.ServiceType; @@ -139,9 +159,13 @@ import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; +import org.thingsboard.server.transport.AbstractTransportIntegrationTest; +import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -172,6 +196,9 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { @Autowired private EdgeEventService edgeEventService; + @Autowired + private DataDecodingEncodingService dataDecodingEncodingService; + @Autowired private TbClusterService clusterService; @@ -201,12 +228,36 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { installation(); edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret()); - edgeImitator.expectMessageAmount(14); + edgeImitator.expectMessageAmount(15); edgeImitator.connect(); + requestEdgeRuleChainMetadata(); + verifyEdgeConnectionAndInitialData(); } + private void requestEdgeRuleChainMetadata() throws Exception { + RuleChainId rootRuleChainId = getEdgeRootRuleChainId(); + RuleChainMetadataRequestMsg.Builder builder = RuleChainMetadataRequestMsg.newBuilder() + .setRuleChainIdMSB(rootRuleChainId.getId().getMostSignificantBits()) + .setRuleChainIdLSB(rootRuleChainId.getId().getLeastSignificantBits()); + testAutoGeneratedCodeByProtobuf(builder); + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder() + .addRuleChainMetadataRequestMsg(builder.build()); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + } + + private RuleChainId getEdgeRootRuleChainId() throws Exception { + List edgeRuleChains = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/ruleChains?", + new TypeReference>() {}, new PageLink(100)).getData(); + for (RuleChain edgeRuleChain : edgeRuleChains) { + if (edgeRuleChain.isRoot()) { + return edgeRuleChain.getId(); + } + } + throw new RuntimeException("Root rule chain not found"); + } + @After public void afterTest() throws Exception { try { @@ -222,10 +273,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { private void installation() throws Exception { edge = doPost("/api/edge", constructEdge("Test Edge", "test"), Edge.class); - MqttDeviceProfileTransportConfiguration transportConfiguration = new MqttDeviceProfileTransportConfiguration(); - transportConfiguration.setTransportPayloadTypeConfiguration(new JsonTransportPayloadConfiguration()); - - thermostatDeviceProfile = this.createDeviceProfile(THERMOSTAT_DEVICE_PROFILE_NAME, transportConfiguration); + thermostatDeviceProfile = this.createDeviceProfile(THERMOSTAT_DEVICE_PROFILE_NAME, + createMqttDeviceProfileTransportConfiguration(new JsonTransportPayloadConfiguration(), false)); extendDeviceProfileData(thermostatDeviceProfile); thermostatDeviceProfile = doPost("/api/deviceProfile", thermostatDeviceProfile, DeviceProfile.class); @@ -281,7 +330,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { DeviceUpdateMsg deviceUpdateMsg = deviceUpdateMsgOpt.get(); Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType()); UUID deviceUUID = new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB()); - Device device = doGet("/api/device/" + deviceUUID.toString(), Device.class); + Device device = doGet("/api/device/" + deviceUUID, Device.class); Assert.assertNotNull(device); List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?", new TypeReference>() {}, new PageLink(100)).getData(); @@ -295,7 +344,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { DeviceProfileUpdateMsg deviceProfileUpdateMsg = deviceProfileUpdateMsgOpt.get(); Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); UUID deviceProfileUUID = new UUID(deviceProfileUpdateMsg.getIdMSB(), deviceProfileUpdateMsg.getIdLSB()); - DeviceProfile deviceProfile = doGet("/api/deviceProfile/" + deviceProfileUUID.toString(), DeviceProfile.class); + DeviceProfile deviceProfile = doGet("/api/deviceProfile/" + deviceProfileUUID, DeviceProfile.class); Assert.assertNotNull(deviceProfile); Assert.assertNotNull(deviceProfile.getProfileData()); Assert.assertNotNull(deviceProfile.getProfileData().getAlarms()); @@ -321,7 +370,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { RuleChainUpdateMsg ruleChainUpdateMsg = ruleChainUpdateMsgOpt.get(); Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, ruleChainUpdateMsg.getMsgType()); UUID ruleChainUUID = new UUID(ruleChainUpdateMsg.getIdMSB(), ruleChainUpdateMsg.getIdLSB()); - RuleChain ruleChain = doGet("/api/ruleChain/" + ruleChainUUID.toString(), RuleChain.class); + RuleChain ruleChain = doGet("/api/ruleChain/" + ruleChainUUID, RuleChain.class); Assert.assertNotNull(ruleChain); List edgeRuleChains = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/ruleChains?", new TypeReference>() {}, new PageLink(100)).getData(); @@ -329,6 +378,13 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { testAutoGeneratedCodeByProtobuf(ruleChainUpdateMsg); + Optional ruleChainMetadataUpdateOpt = edgeImitator.findMessageByType(RuleChainMetadataUpdateMsg.class); + Assert.assertTrue(ruleChainMetadataUpdateOpt.isPresent()); + RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = ruleChainMetadataUpdateOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, ruleChainMetadataUpdateMsg.getMsgType()); + Assert.assertEquals(ruleChainUpdateMsg.getIdMSB(), ruleChainMetadataUpdateMsg.getRuleChainIdMSB()); + Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), ruleChainMetadataUpdateMsg.getRuleChainIdLSB()); + validateAdminSettings(); } @@ -408,6 +464,226 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits()); } + @Test + public void testDeviceProfiles_snmp() throws Exception { + DeviceProfile deviceProfile = createDeviceProfileAndDoBasicAssert("SNMP", createSnmpDeviceProfileTransportConfiguration()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); + DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); + Assert.assertEquals(deviceProfileUpdateMsg.getIdMSB(), deviceProfile.getUuidId().getMostSignificantBits()); + Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits()); + Assert.assertEquals(DeviceTransportType.SNMP.name(), deviceProfileUpdateMsg.getTransportType()); + + Optional deviceProfileDataOpt = + dataDecodingEncodingService.decode(deviceProfileUpdateMsg.getProfileDataBytes().toByteArray()); + + Assert.assertTrue(deviceProfileDataOpt.isPresent()); + DeviceProfileData deviceProfileData = deviceProfileDataOpt.get(); + + Assert.assertTrue(deviceProfileData.getTransportConfiguration() instanceof SnmpDeviceProfileTransportConfiguration); + SnmpDeviceProfileTransportConfiguration transportConfiguration = + (SnmpDeviceProfileTransportConfiguration) deviceProfileData.getTransportConfiguration(); + Assert.assertEquals(Integer.valueOf(1000), transportConfiguration.getTimeoutMs()); + Assert.assertEquals(Integer.valueOf(3), transportConfiguration.getRetries()); + + Assert.assertFalse(transportConfiguration.getCommunicationConfigs().isEmpty()); + SnmpCommunicationConfig communicationConfig = transportConfiguration.getCommunicationConfigs().get(0); + Assert.assertTrue(communicationConfig instanceof TelemetryQueryingSnmpCommunicationConfig); + TelemetryQueryingSnmpCommunicationConfig snmpCommunicationConfig = + (TelemetryQueryingSnmpCommunicationConfig) communicationConfig; + + Assert.assertEquals(Long.valueOf(500L), snmpCommunicationConfig.getQueryingFrequencyMs()); + Assert.assertFalse(snmpCommunicationConfig.getMappings().isEmpty()); + + SnmpMapping snmpMapping = snmpCommunicationConfig.getMappings().get(0); + Assert.assertEquals("temperature", snmpMapping.getKey()); + Assert.assertEquals("1.3.3.5.6.7.8.9.1", snmpMapping.getOid()); + Assert.assertEquals(DataType.DOUBLE, snmpMapping.getDataType()); + + removeDeviceProfileAndDoBasicAssert(deviceProfile); + } + + @Test + public void testDeviceProfiles_lwm2m() throws Exception { + DeviceProfile deviceProfile = createDeviceProfileAndDoBasicAssert("LWM2M", createLwm2mDeviceProfileTransportConfiguration()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); + DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); + Assert.assertEquals(deviceProfileUpdateMsg.getIdMSB(), deviceProfile.getUuidId().getMostSignificantBits()); + Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits()); + Assert.assertEquals(DeviceTransportType.LWM2M.name(), deviceProfileUpdateMsg.getTransportType()); + + Optional deviceProfileDataOpt = + dataDecodingEncodingService.decode(deviceProfileUpdateMsg.getProfileDataBytes().toByteArray()); + + Assert.assertTrue(deviceProfileDataOpt.isPresent()); + DeviceProfileData deviceProfileData = deviceProfileDataOpt.get(); + + Assert.assertTrue(deviceProfileData.getTransportConfiguration() instanceof Lwm2mDeviceProfileTransportConfiguration); + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = + (Lwm2mDeviceProfileTransportConfiguration) deviceProfileData.getTransportConfiguration(); + + OtherConfiguration clientLwM2mSettings = transportConfiguration.getClientLwM2mSettings(); + Assert.assertEquals(PowerMode.DRX, clientLwM2mSettings.getPowerMode()); + Assert.assertEquals(Integer.valueOf(1), clientLwM2mSettings.getFwUpdateStrategy()); + Assert.assertEquals(Integer.valueOf(1), clientLwM2mSettings.getSwUpdateStrategy()); + Assert.assertEquals(Integer.valueOf(1), clientLwM2mSettings.getClientOnlyObserveAfterConnect()); + + Assert.assertTrue(transportConfiguration.isBootstrapServerUpdateEnable()); + + Assert.assertFalse(transportConfiguration.getBootstrap().isEmpty()); + LwM2MBootstrapServerCredential lwM2MBootstrapServerCredential = transportConfiguration.getBootstrap().get(0); + Assert.assertTrue(lwM2MBootstrapServerCredential instanceof NoSecLwM2MBootstrapServerCredential); + NoSecLwM2MBootstrapServerCredential noSecLwM2MBootstrapServerCredential = (NoSecLwM2MBootstrapServerCredential) lwM2MBootstrapServerCredential; + + Assert.assertEquals("PUBLIC_KEY", noSecLwM2MBootstrapServerCredential.getServerPublicKey()); + Assert.assertEquals(Integer.valueOf(123), noSecLwM2MBootstrapServerCredential.getShortServerId()); + Assert.assertTrue(noSecLwM2MBootstrapServerCredential.isBootstrapServerIs()); + Assert.assertEquals("localhost", noSecLwM2MBootstrapServerCredential.getHost()); + Assert.assertEquals(Integer.valueOf(5687), noSecLwM2MBootstrapServerCredential.getPort()); + + TelemetryMappingConfiguration observeAttr = transportConfiguration.getObserveAttr(); + Assert.assertEquals("batteryLevel", observeAttr.getKeyName().get("/3_1.0/0/9")); + Assert.assertTrue(observeAttr.getObserve().isEmpty()); + Assert.assertTrue(observeAttr.getAttribute().isEmpty()); + Assert.assertFalse(observeAttr.getTelemetry().isEmpty()); + Assert.assertTrue(observeAttr.getTelemetry().contains("/3_1.0/0/9")); + Assert.assertTrue(observeAttr.getAttributeLwm2m().isEmpty()); + + removeDeviceProfileAndDoBasicAssert(deviceProfile); + } + + @Test + public void testDeviceProfiles_coap() throws Exception { + DeviceProfile deviceProfile = createDeviceProfileAndDoBasicAssert("COAP", createCoapDeviceProfileTransportConfiguration()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); + DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); + Assert.assertEquals(deviceProfileUpdateMsg.getIdMSB(), deviceProfile.getUuidId().getMostSignificantBits()); + Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits()); + Assert.assertEquals(DeviceTransportType.COAP.name(), deviceProfileUpdateMsg.getTransportType()); + + Optional deviceProfileDataOpt = + dataDecodingEncodingService.decode(deviceProfileUpdateMsg.getProfileDataBytes().toByteArray()); + + Assert.assertTrue(deviceProfileDataOpt.isPresent()); + DeviceProfileData deviceProfileData = deviceProfileDataOpt.get(); + + Assert.assertTrue(deviceProfileData.getTransportConfiguration() instanceof CoapDeviceProfileTransportConfiguration); + CoapDeviceProfileTransportConfiguration transportConfiguration = + (CoapDeviceProfileTransportConfiguration) deviceProfileData.getTransportConfiguration(); + + PowerSavingConfiguration clientSettings = transportConfiguration.getClientSettings(); + + Assert.assertEquals(PowerMode.DRX, clientSettings.getPowerMode()); + Assert.assertEquals(Long.valueOf(1L), clientSettings.getEdrxCycle()); + Assert.assertEquals(Long.valueOf(1L), clientSettings.getPsmActivityTimer()); + Assert.assertEquals(Long.valueOf(1L), clientSettings.getPagingTransmissionWindow()); + + Assert.assertTrue(transportConfiguration.getCoapDeviceTypeConfiguration() instanceof DefaultCoapDeviceTypeConfiguration); + DefaultCoapDeviceTypeConfiguration coapDeviceTypeConfiguration = + (DefaultCoapDeviceTypeConfiguration) transportConfiguration.getCoapDeviceTypeConfiguration(); + + Assert.assertTrue(coapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration); + + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = + (ProtoTransportPayloadConfiguration) coapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); + + Assert.assertEquals(AbstractTransportIntegrationTest.DEVICE_TELEMETRY_PROTO_SCHEMA, protoTransportPayloadConfiguration.getDeviceTelemetryProtoSchema()); + Assert.assertEquals(AbstractTransportIntegrationTest.DEVICE_ATTRIBUTES_PROTO_SCHEMA, protoTransportPayloadConfiguration.getDeviceAttributesProtoSchema()); + Assert.assertEquals(AbstractTransportIntegrationTest.DEVICE_RPC_RESPONSE_PROTO_SCHEMA, protoTransportPayloadConfiguration.getDeviceRpcResponseProtoSchema()); + Assert.assertEquals(AbstractTransportIntegrationTest.DEVICE_RPC_REQUEST_PROTO_SCHEMA, protoTransportPayloadConfiguration.getDeviceRpcRequestProtoSchema()); + + removeDeviceProfileAndDoBasicAssert(deviceProfile); + } + + private DeviceProfile createDeviceProfileAndDoBasicAssert(String deviceProfileName, DeviceProfileTransportConfiguration deviceProfileTransportConfiguration) throws Exception { + DeviceProfile deviceProfile = this.createDeviceProfile(deviceProfileName, deviceProfileTransportConfiguration); + extendDeviceProfileData(deviceProfile); + edgeImitator.expectMessageAmount(1); + deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + return deviceProfile; + } + + private void removeDeviceProfileAndDoBasicAssert(DeviceProfile deviceProfile) throws Exception { + edgeImitator.expectMessageAmount(1); + doDelete("/api/deviceProfile/" + deviceProfile.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); + DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); + Assert.assertEquals(deviceProfileUpdateMsg.getIdMSB(), deviceProfile.getUuidId().getMostSignificantBits()); + Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits()); + } + + private SnmpDeviceProfileTransportConfiguration createSnmpDeviceProfileTransportConfiguration() { + SnmpDeviceProfileTransportConfiguration transportConfiguration = new SnmpDeviceProfileTransportConfiguration(); + List communicationConfigs = new ArrayList<>(); + TelemetryQueryingSnmpCommunicationConfig communicationConfig = new TelemetryQueryingSnmpCommunicationConfig(); + communicationConfig.setQueryingFrequencyMs(500L); + List mappings = new ArrayList<>(); + mappings.add(new SnmpMapping("1.3.3.5.6.7.8.9.1", "temperature", DataType.DOUBLE)); + communicationConfig.setMappings(mappings); + communicationConfigs.add(communicationConfig); + transportConfiguration.setCommunicationConfigs(communicationConfigs); + transportConfiguration.setTimeoutMs(1000); + transportConfiguration.setRetries(3); + return transportConfiguration; + } + + private Lwm2mDeviceProfileTransportConfiguration createLwm2mDeviceProfileTransportConfiguration() { + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration(); + + OtherConfiguration clientLwM2mSettings = JacksonUtil.fromString(AbstractLwM2MIntegrationTest.CLIENT_LWM2M_SETTINGS, OtherConfiguration.class); + transportConfiguration.setClientLwM2mSettings(clientLwM2mSettings); + + transportConfiguration.setBootstrapServerUpdateEnable(true); + + TelemetryMappingConfiguration observeAttrConfiguration = + JacksonUtil.fromString(AbstractLwM2MIntegrationTest.OBSERVE_ATTRIBUTES_WITH_PARAMS, TelemetryMappingConfiguration.class); + transportConfiguration.setObserveAttr(observeAttrConfiguration); + + List bootstrap = new ArrayList<>(); + AbstractLwM2MBootstrapServerCredential bootstrapServerCredential = new NoSecLwM2MBootstrapServerCredential(); + bootstrapServerCredential.setServerPublicKey("PUBLIC_KEY"); + bootstrapServerCredential.setShortServerId(123); + bootstrapServerCredential.setBootstrapServerIs(true); + bootstrapServerCredential.setHost("localhost"); + bootstrapServerCredential.setPort(5687); + bootstrap.add(bootstrapServerCredential); + transportConfiguration.setBootstrap(bootstrap); + + return transportConfiguration; + } + + private CoapDeviceProfileTransportConfiguration createCoapDeviceProfileTransportConfiguration() { + CoapDeviceProfileTransportConfiguration transportConfiguration = new CoapDeviceProfileTransportConfiguration(); + PowerSavingConfiguration clientSettings = new PowerSavingConfiguration(); + clientSettings.setPowerMode(PowerMode.DRX); + clientSettings.setEdrxCycle(1L); + clientSettings.setPsmActivityTimer(1L); + clientSettings.setPagingTransmissionWindow(1L); + transportConfiguration.setClientSettings(clientSettings); + DefaultCoapDeviceTypeConfiguration coapDeviceTypeConfiguration = new DefaultCoapDeviceTypeConfiguration(); + ProtoTransportPayloadConfiguration transportPayloadTypeConfiguration = new ProtoTransportPayloadConfiguration(); + transportPayloadTypeConfiguration.setDeviceTelemetryProtoSchema(AbstractTransportIntegrationTest.DEVICE_TELEMETRY_PROTO_SCHEMA); + transportPayloadTypeConfiguration.setDeviceAttributesProtoSchema(AbstractTransportIntegrationTest.DEVICE_ATTRIBUTES_PROTO_SCHEMA); + transportPayloadTypeConfiguration.setDeviceRpcResponseProtoSchema(AbstractTransportIntegrationTest.DEVICE_RPC_RESPONSE_PROTO_SCHEMA); + transportPayloadTypeConfiguration.setDeviceRpcRequestProtoSchema(AbstractTransportIntegrationTest.DEVICE_RPC_REQUEST_PROTO_SCHEMA); + coapDeviceTypeConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); + transportConfiguration.setCoapDeviceTypeConfiguration(coapDeviceTypeConfiguration); + return transportConfiguration; + } + @Test public void testDevices() throws Exception { // 1 @@ -1233,7 +1509,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { @Test public void testSendDeviceToCloudWithNameThatAlreadyExistsOnCloud() throws Exception { - String deviceOnCloudName = RandomStringUtils.randomAlphanumeric(15); + String deviceOnCloudName = StringUtils.randomAlphanumeric(15); Device deviceOnCloud = saveDevice(deviceOnCloudName, "Default"); UUID uuid = Uuids.timeBased(); @@ -1878,8 +2154,16 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { OtaPackageInfo firmwareOtaPackageInfo = saveOtaPackageInfo(thermostatDeviceProfile.getId()); Assert.assertTrue(edgeImitator.waitForMessages()); - Device savedDevice = saveDevice(RandomStringUtils.randomAlphanumeric(15), thermostatDeviceProfile.getName()); + Device savedDevice = saveDevice(StringUtils.randomAlphanumeric(15), thermostatDeviceProfile.getName()); savedDevice.setFirmwareId(firmwareOtaPackageInfo.getId()); + + DeviceData deviceData = new DeviceData(); + deviceData.setConfiguration(new DefaultDeviceConfiguration()); + MqttDeviceTransportConfiguration transportConfiguration = new MqttDeviceTransportConfiguration(); + transportConfiguration.getProperties().put("topic", "tb_rule_engine.thermostat"); + deviceData.setTransportConfiguration(transportConfiguration); + savedDevice.setDeviceData(deviceData); + savedDevice = doPost("/api/device", savedDevice, Device.class); // wait until device UPDATED event is sent to edge notification service @@ -1899,6 +2183,14 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(deviceUpdateMsg.getType(), savedDevice.getType()); Assert.assertEquals(firmwareOtaPackageInfo.getUuidId().getMostSignificantBits(), deviceUpdateMsg.getFirmwareIdMSB()); Assert.assertEquals(firmwareOtaPackageInfo.getUuidId().getLeastSignificantBits(), deviceUpdateMsg.getFirmwareIdLSB()); + Optional deviceDataOpt = + dataDecodingEncodingService.decode(deviceUpdateMsg.getDeviceDataBytes().toByteArray()); + Assert.assertTrue(deviceDataOpt.isPresent()); + deviceData = deviceDataOpt.get(); + Assert.assertTrue(deviceData.getTransportConfiguration() instanceof MqttDeviceTransportConfiguration); + MqttDeviceTransportConfiguration mqttDeviceTransportConfiguration = + (MqttDeviceTransportConfiguration) deviceData.getTransportConfiguration(); + Assert.assertEquals("tb_rule_engine.thermostat", mqttDeviceTransportConfiguration.getProperties().get("topic")); return savedDevice; } @@ -1942,7 +2234,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest(); firmwareInfo.setDeviceProfileId(deviceProfileId); firmwareInfo.setType(FIRMWARE); - firmwareInfo.setTitle("Firmware Edge " + RandomStringUtils.randomAlphanumeric(3)); + firmwareInfo.setTitle("Firmware Edge " + StringUtils.randomAlphanumeric(3)); firmwareInfo.setVersion("v1.0"); firmwareInfo.setTag("My firmware #1 v1.0"); firmwareInfo.setUsesUrl(true); diff --git a/application/src/test/java/org/thingsboard/server/service/sms/smpp/SmppSmsSenderTest.java b/application/src/test/java/org/thingsboard/server/service/sms/smpp/SmppSmsSenderTest.java index 7cbc8c03d2..220013b430 100644 --- a/application/src/test/java/org/thingsboard/server/service/sms/smpp/SmppSmsSenderTest.java +++ b/application/src/test/java/org/thingsboard/server/service/sms/smpp/SmppSmsSenderTest.java @@ -15,28 +15,23 @@ */ package org.thingsboard.server.service.sms.smpp; -import org.apache.commons.lang3.StringUtils; -import org.assertj.core.api.Assertions; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import org.smpp.Session; import org.smpp.pdu.SubmitSMResp; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.sms.config.SmppSmsProviderConfiguration; import java.lang.reflect.Constructor; -import java.net.UnknownHostException; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; diff --git a/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java index 09acd96d47..bdda6d52ed 100644 --- a/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java @@ -33,7 +33,7 @@ public abstract class AbstractTransportIntegrationTest extends AbstractControlle protected static final AtomicInteger atomicInteger = new AtomicInteger(2); - protected static final String DEVICE_TELEMETRY_PROTO_SCHEMA = "syntax =\"proto3\";\n" + + public static final String DEVICE_TELEMETRY_PROTO_SCHEMA = "syntax =\"proto3\";\n" + "\n" + "package test;\n" + "\n" + @@ -54,7 +54,7 @@ public abstract class AbstractTransportIntegrationTest extends AbstractControlle " }\n" + "}"; - protected static final String DEVICE_ATTRIBUTES_PROTO_SCHEMA = "syntax =\"proto3\";\n" + + public static final String DEVICE_ATTRIBUTES_PROTO_SCHEMA = "syntax =\"proto3\";\n" + "\n" + "package test;\n" + "\n" + @@ -75,14 +75,14 @@ public abstract class AbstractTransportIntegrationTest extends AbstractControlle " }\n" + "}"; - protected static final String DEVICE_RPC_RESPONSE_PROTO_SCHEMA = "syntax =\"proto3\";\n" + + public static final String DEVICE_RPC_RESPONSE_PROTO_SCHEMA = "syntax =\"proto3\";\n" + "package rpc;\n" + "\n" + "message RpcResponseMsg {\n" + " optional string payload = 1;\n" + "}"; - protected static final String DEVICE_RPC_REQUEST_PROTO_SCHEMA = "syntax =\"proto3\";\n" + + public static final String DEVICE_RPC_REQUEST_PROTO_SCHEMA = "syntax =\"proto3\";\n" + "package rpc;\n" + "\n" + "message RpcRequestMsg {\n" + diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java index ca5b40efef..2d034d3b41 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java @@ -22,13 +22,13 @@ import com.google.protobuf.DynamicMessage; import com.google.protobuf.InvalidProtocolBufferException; import com.squareup.wire.schema.internal.parser.ProtoFileElement; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.eclipse.californium.core.CoapHandler; import org.eclipse.californium.core.CoapObserveRelation; import org.eclipse.californium.core.CoapResponse; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 9f04d382fb..ac272d0bb4 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -144,7 +144,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractControllerTes " \"attributeLwm2m\": {}\n" + " }"; - protected final String OBSERVE_ATTRIBUTES_WITH_PARAMS = + public static final String OBSERVE_ATTRIBUTES_WITH_PARAMS = " {\n" + " \"keyName\": {\n" + @@ -159,7 +159,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractControllerTes " \"attributeLwm2m\": {}\n" + " }"; - protected final String CLIENT_LWM2M_SETTINGS = + public static final String CLIENT_LWM2M_SETTINGS = " {\n" + " \"edrxCycle\": null,\n" + " \"powerMode\": \"DRX\",\n" + diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java index 9fbdff3fa5..917e41d1b4 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java @@ -17,19 +17,14 @@ package org.thingsboard.server.transport.mqtt; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; -import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.eclipse.paho.client.mqttv3.MqttMessage; -import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import org.springframework.test.context.TestPropertySource; -import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/credentials/BasicMqttCredentialsTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/credentials/BasicMqttCredentialsTest.java index 8ef02a63dc..0884f1457c 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/credentials/BasicMqttCredentialsTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/credentials/BasicMqttCredentialsTest.java @@ -16,12 +16,12 @@ package org.thingsboard.server.transport.mqtt.credentials; import com.fasterxml.jackson.core.type.TypeReference; -import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.paho.client.mqttv3.MqttSecurityException; import org.junit.Before; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; @@ -102,8 +102,8 @@ public class BasicMqttCredentialsTest extends AbstractMqttIntegrationTest { mqttTestClient4.connectAndWait(USER_NAME2, PASSWORD); // Also correct. Random clientId and password, but matches access token - MqttTestClient mqttTestClient5 = new MqttTestClient(RandomStringUtils.randomAlphanumeric(10)); - mqttTestClient5.connectAndWait(USER_NAME2, RandomStringUtils.randomAlphanumeric(10)); + MqttTestClient mqttTestClient5 = new MqttTestClient(StringUtils.randomAlphanumeric(10)); + mqttTestClient5.connectAndWait(USER_NAME2, StringUtils.randomAlphanumeric(10)); testTelemetryIsDelivered(accessTokenDevice, mqttTestClient1); testTelemetryIsDelivered(clientIdDevice, mqttTestClient2); @@ -131,7 +131,7 @@ public class BasicMqttCredentialsTest extends AbstractMqttIntegrationTest { } private void testTelemetryIsDelivered(Device device, MqttTestClient client, boolean ok) throws Exception { - String randomKey = RandomStringUtils.randomAlphanumeric(10); + String randomKey = StringUtils.randomAlphanumeric(10); List expectedKeys = Arrays.asList(randomKey); client.publishAndWait(DEVICE_TELEMETRY_TOPIC, JacksonUtil.toString(JacksonUtil.newObjectNode().put(randomKey, true)).getBytes()); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java index 0abd0f1d6c..067af192e4 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java @@ -25,12 +25,12 @@ import com.nimbusds.jose.util.StandardCharset; import com.squareup.wire.schema.internal.parser.ProtoFileElement; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; diff --git a/common/actor/pom.xml b/common/actor/pom.xml index a565ed9052..268b8e698b 100644 --- a/common/actor/pom.xml +++ b/common/actor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT common org.thingsboard.common diff --git a/common/cache/pom.xml b/common/cache/pom.xml index d3f2c838c8..e2b743574f 100644 --- a/common/cache/pom.xml +++ b/common/cache/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT common org.thingsboard.common diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisClusterConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisClusterConfiguration.java index 2cf67e35aa..f68dabf9eb 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisClusterConfiguration.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisClusterConfiguration.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.cache; -import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; diff --git a/common/cluster-api/pom.xml b/common/cluster-api/pom.xml index 62175cae08..3df2d5e8bc 100644 --- a/common/cluster-api/pom.xml +++ b/common/cluster-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT common org.thingsboard.common diff --git a/common/coap-server/pom.xml b/common/coap-server/pom.xml index 5eba5c7175..596aa90e98 100644 --- a/common/coap-server/pom.xml +++ b/common/coap-server/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT common org.thingsboard.common diff --git a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java index ce8b093b4d..15807c4154 100644 --- a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java +++ b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java @@ -27,7 +27,7 @@ import org.eclipse.californium.scandium.dtls.HandshakeException; import org.eclipse.californium.scandium.dtls.HandshakeResultHandler; import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier; import org.eclipse.californium.scandium.util.ServerNames; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.msg.EncryptionUtil; diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml index 4b15effa44..c75186c0a0 100644 --- a/common/dao-api/pom.xml +++ b/common/dao-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT common org.thingsboard.common diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/CassandraDriverOptions.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/CassandraDriverOptions.java index 986be6d515..3c6d8ce1dd 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/CassandraDriverOptions.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/CassandraDriverOptions.java @@ -23,7 +23,7 @@ import com.datastax.oss.driver.api.core.config.ProgrammaticDriverConfigLoaderBui import com.datastax.oss.driver.api.core.metrics.DefaultSessionMetric; import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric; import lombok.Data; -import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java index b8688b1524..cf17eec88d 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java @@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult; import org.thingsboard.server.common.data.kv.TsKvEntry; @@ -32,6 +33,8 @@ import java.util.List; */ public interface TimeseriesService { + ListenableFuture> findAllByQueries(TenantId tenantId, EntityId entityId, List queries); + ListenableFuture> findAll(TenantId tenantId, EntityId entityId, List queries); ListenableFuture> findLatest(TenantId tenantId, EntityId entityId, Collection keys); diff --git a/common/data/pom.xml b/common/data/pom.xml index 75758021c8..decbba55aa 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT common org.thingsboard.common diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityFieldsData.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityFieldsData.java index c3cd2b12b4..04c526b6fe 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityFieldsData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityFieldsData.java @@ -50,6 +50,10 @@ public class EntityFieldsData { } public String getFieldValue(String field) { + return getFieldValue(field, false); + } + + public String getFieldValue(String field, boolean ignoreNullStrings) { String[] fieldsTree = field.split("\\."); JsonNode current = fieldsData; for (String key : fieldsTree) { @@ -61,6 +65,9 @@ public class EntityFieldsData { } } if (current != null) { + if(current.isNull() && ignoreNullStrings){ + return null; + } if (current.isValueNode()) { return current.asText(); } else { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java index f76757f522..18d3dfff1e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java @@ -16,11 +16,11 @@ package org.thingsboard.server.common.data; import com.google.common.base.Splitter; +import org.apache.commons.lang3.RandomStringUtils; import static org.apache.commons.lang3.StringUtils.repeat; public class StringUtils { - public static final String EMPTY = ""; public static final int INDEX_NOT_FOUND = -1; @@ -45,7 +45,7 @@ public class StringUtils { if (isEmpty(str) || isEmpty(remove)) { return str; } - if (str.startsWith(remove)){ + if (str.startsWith(remove)) { return str.substring(remove.length()); } return str; @@ -98,4 +98,72 @@ public class StringUtils { return Splitter.fixedLength(maxPartSize).split(value); } + public static boolean equalsIgnoreCase(String str1, String str2) { + return str1 == null ? str2 == null : str1.equalsIgnoreCase(str2); + } + + public static String join(String[] keyArray, String lwm2mSeparatorPath) { + return org.apache.commons.lang3.StringUtils.join(keyArray, lwm2mSeparatorPath); + } + + public static String trimToNull(String toString) { + return org.apache.commons.lang3.StringUtils.trimToNull(toString); + } + + public static boolean isNoneEmpty(String str) { + return org.apache.commons.lang3.StringUtils.isNoneEmpty(str); + } + + public static boolean endsWith(String str, String suffix) { + return org.apache.commons.lang3.StringUtils.endsWith(str, suffix); + } + + public static boolean hasLength(String str) { + return org.springframework.util.StringUtils.hasLength(str); + } + + public static boolean isNoneBlank(String... str) { + return org.apache.commons.lang3.StringUtils.isNoneBlank(str); + } + + public static boolean hasText(String str) { + return org.springframework.util.StringUtils.hasText(str); + } + + public static String defaultString(String s, String defaultValue) { + return org.apache.commons.lang3.StringUtils.defaultString(s, defaultValue); + } + + public static boolean isNumeric(String str) { + return org.apache.commons.lang3.StringUtils.isNumeric(str); + } + + public static boolean equals(String str1, String str2) { + return org.apache.commons.lang3.StringUtils.equals(str1, str2); + } + + public static String substringAfterLast(String str, String sep) { + return org.apache.commons.lang3.StringUtils.substringAfterLast(str, sep); + } + + public static String randomNumeric(int length) { + return RandomStringUtils.randomNumeric(length); + } + + public static String random(int length) { + return RandomStringUtils.random(length); + } + + public static String random(int length, String chars) { + return RandomStringUtils.random(length, chars); + } + + public static String randomAlphanumeric(int count) { + return RandomStringUtils.randomAlphanumeric(count); + } + + public static String randomAlphabetic(int count) { + return RandomStringUtils.randomAlphabetic(count); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/CoapDeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/CoapDeviceTransportConfiguration.java index f38749a868..e603d74198 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/CoapDeviceTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/CoapDeviceTransportConfiguration.java @@ -27,6 +27,8 @@ import java.util.Map; @Data public class CoapDeviceTransportConfiguration extends PowerSavingConfiguration implements DeviceTransportConfiguration { + private static final long serialVersionUID = 6061442236008925609L; + @JsonIgnore private Map properties = new HashMap<>(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DefaultDeviceConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DefaultDeviceConfiguration.java index 3e03dffd66..268cf2da6e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DefaultDeviceConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DefaultDeviceConfiguration.java @@ -23,6 +23,8 @@ import org.thingsboard.server.common.data.DeviceProfileType; @Data public class DefaultDeviceConfiguration implements DeviceConfiguration { + private static final long serialVersionUID = -2225378639573611325L; + @Override public DeviceProfileType getType() { return DeviceProfileType.DEFAULT; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java index 83ed57fb84..c7f53a84c1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java @@ -22,6 +22,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import io.swagger.annotations.ApiModel; import org.thingsboard.server.common.data.DeviceProfileType; +import java.io.Serializable; + @ApiModel @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo( @@ -30,7 +32,7 @@ import org.thingsboard.server.common.data.DeviceProfileType; property = "type") @JsonSubTypes({ @JsonSubTypes.Type(value = DefaultDeviceConfiguration.class, name = "DEFAULT")}) -public interface DeviceConfiguration { +public interface DeviceConfiguration extends Serializable { @JsonIgnore DeviceProfileType getType(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceData.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceData.java index 03187f180b..8a87c18059 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceData.java @@ -19,9 +19,13 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import java.io.Serializable; + @ApiModel @Data -public class DeviceData { +public class DeviceData implements Serializable { + + private static final long serialVersionUID = -3771567735290681274L; @ApiModelProperty(position = 1, value = "Device configuration for device profile type. DEFAULT is only supported value for now") private DeviceConfiguration configuration; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/PowerSavingConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/PowerSavingConfiguration.java index ebed17c585..51870b67c9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/PowerSavingConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/PowerSavingConfiguration.java @@ -17,8 +17,13 @@ package org.thingsboard.server.common.data.device.data; import lombok.Data; +import java.io.Serializable; + @Data -public class PowerSavingConfiguration { +public class PowerSavingConfiguration implements Serializable { + + private static final long serialVersionUID = 2905389805488525362L; + private PowerMode powerMode; private Long psmActivityTimer; private Long edrxCycle; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java index 041fe4752c..e3f46a532b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java @@ -18,15 +18,12 @@ package org.thingsboard.server.common.data.device.data; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import lombok.ToString; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.transport.snmp.AuthenticationProtocol; import org.thingsboard.server.common.data.transport.snmp.PrivacyProtocol; import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; -import java.util.Objects; - @Data @ToString(of = {"host", "port", "protocolVersion"}) public class SnmpDeviceTransportConfiguration implements DeviceTransportConfiguration { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CoapDeviceTypeConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CoapDeviceTypeConfiguration.java index e60f421635..a4bbe9c2c3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CoapDeviceTypeConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CoapDeviceTypeConfiguration.java @@ -21,6 +21,8 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.thingsboard.server.common.data.CoapDeviceType; +import java.io.Serializable; + @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, @@ -29,7 +31,7 @@ import org.thingsboard.server.common.data.CoapDeviceType; @JsonSubTypes({ @JsonSubTypes.Type(value = DefaultCoapDeviceTypeConfiguration.class, name = "DEFAULT"), @JsonSubTypes.Type(value = EfentoCoapDeviceTypeConfiguration.class, name = "EFENTO")}) -public interface CoapDeviceTypeConfiguration { +public interface CoapDeviceTypeConfiguration extends Serializable { @JsonIgnore CoapDeviceType getCoapDeviceType(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DefaultCoapDeviceTypeConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DefaultCoapDeviceTypeConfiguration.java index c835d58af4..cfa5c08cd1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DefaultCoapDeviceTypeConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DefaultCoapDeviceTypeConfiguration.java @@ -21,6 +21,8 @@ import org.thingsboard.server.common.data.CoapDeviceType; @Data public class DefaultCoapDeviceTypeConfiguration implements CoapDeviceTypeConfiguration { + private static final long serialVersionUID = -4287100699186773773L; + private TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/EfentoCoapDeviceTypeConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/EfentoCoapDeviceTypeConfiguration.java index 4ac85b4336..29cc1983e6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/EfentoCoapDeviceTypeConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/EfentoCoapDeviceTypeConfiguration.java @@ -21,6 +21,8 @@ import org.thingsboard.server.common.data.CoapDeviceType; @Data public class EfentoCoapDeviceTypeConfiguration implements CoapDeviceTypeConfiguration { + private static final long serialVersionUID = -8523081152598707064L; + @Override public CoapDeviceType getCoapDeviceType() { return CoapDeviceType.EFENTO; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/ObjectAttributes.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/ObjectAttributes.java index bcaedaa801..60245f6869 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/ObjectAttributes.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/ObjectAttributes.java @@ -18,9 +18,13 @@ package org.thingsboard.server.common.data.device.profile.lwm2m; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; +import java.io.Serializable; + @Data @JsonInclude(JsonInclude.Include.NON_NULL) -public class ObjectAttributes { +public class ObjectAttributes implements Serializable { + + private static final long serialVersionUID = 4765123984733721312L; private Long dim; private String ver; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/OtherConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/OtherConfiguration.java index 73caea088d..13bf9f4793 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/OtherConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/OtherConfiguration.java @@ -18,10 +18,12 @@ package org.thingsboard.server.common.data.device.profile.lwm2m; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; +@EqualsAndHashCode(callSuper = true) @Data @NoArgsConstructor @AllArgsConstructor diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/TelemetryMappingConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/TelemetryMappingConfiguration.java index cb6f4e7695..dac6f76e6a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/TelemetryMappingConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/TelemetryMappingConfiguration.java @@ -19,13 +19,16 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; import java.util.Map; import java.util.Set; @Data @NoArgsConstructor @AllArgsConstructor -public class TelemetryMappingConfiguration { +public class TelemetryMappingConfiguration implements Serializable { + + private static final long serialVersionUID = -7594999741305410419L; private Map keyName; private Set observe; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MBootstrapServerCredential.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MBootstrapServerCredential.java index 6a563c8418..6367036ecb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MBootstrapServerCredential.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MBootstrapServerCredential.java @@ -21,6 +21,8 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; +import java.io.Serializable; + @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, property = "securityMode") @@ -31,7 +33,7 @@ import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurity @JsonSubTypes.Type(value = X509LwM2MBootstrapServerCredential.class, name = "X509") }) @JsonIgnoreProperties(ignoreUnknown = true) -public interface LwM2MBootstrapServerCredential { +public interface LwM2MBootstrapServerCredential extends Serializable { @JsonIgnore LwM2MSecurityMode getSecurityMode(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/NoSecLwM2MBootstrapServerCredential.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/NoSecLwM2MBootstrapServerCredential.java index 2c0e615cfb..301d033ab9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/NoSecLwM2MBootstrapServerCredential.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/NoSecLwM2MBootstrapServerCredential.java @@ -18,6 +18,9 @@ package org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; public class NoSecLwM2MBootstrapServerCredential extends AbstractLwM2MBootstrapServerCredential { + + private static final long serialVersionUID = 5540417758424747066L; + @Override public LwM2MSecurityMode getSecurityMode() { return LwM2MSecurityMode.NO_SEC; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/PSKLwM2MBootstrapServerCredential.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/PSKLwM2MBootstrapServerCredential.java index 08901459d2..2fd73af720 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/PSKLwM2MBootstrapServerCredential.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/PSKLwM2MBootstrapServerCredential.java @@ -18,6 +18,9 @@ package org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; public class PSKLwM2MBootstrapServerCredential extends AbstractLwM2MBootstrapServerCredential { + + private static final long serialVersionUID = -1639587501559199887L; + @Override public LwM2MSecurityMode getSecurityMode() { return LwM2MSecurityMode.PSK; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/RPKLwM2MBootstrapServerCredential.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/RPKLwM2MBootstrapServerCredential.java index 477973cf6b..99ecf4dcff 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/RPKLwM2MBootstrapServerCredential.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/RPKLwM2MBootstrapServerCredential.java @@ -18,6 +18,9 @@ package org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; public class RPKLwM2MBootstrapServerCredential extends AbstractLwM2MBootstrapServerCredential { + + private static final long serialVersionUID = 6692464656059120166L; + @Override public LwM2MSecurityMode getSecurityMode() { return LwM2MSecurityMode.RPK; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/X509LwM2MBootstrapServerCredential.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/X509LwM2MBootstrapServerCredential.java index 3f4e0f0aa8..a229d5fceb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/X509LwM2MBootstrapServerCredential.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/X509LwM2MBootstrapServerCredential.java @@ -18,6 +18,9 @@ package org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; public class X509LwM2MBootstrapServerCredential extends AbstractLwM2MBootstrapServerCredential { + + private static final long serialVersionUID = -3740860424558547405L; + @Override public LwM2MSecurityMode getSecurityMode() { return LwM2MSecurityMode.X509; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/AggTsKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/AggTsKvEntry.java new file mode 100644 index 0000000000..85330d3577 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/AggTsKvEntry.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +import lombok.ToString; +import org.thingsboard.server.common.data.query.TsValue; + +@ToString +public class AggTsKvEntry extends BasicTsKvEntry { + + private static final long serialVersionUID = -1933884317450255935L; + + private final long count; + + public AggTsKvEntry(long ts, KvEntry kv, long count) { + super(ts, kv); + this.count = count; + } + + @Override + public TsValue toTsValue() { + return new TsValue(ts, getValueAsString(), count); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java index fc111da8e2..29b98d5e83 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java @@ -31,8 +31,7 @@ public class BaseReadTsKvQuery extends BaseTsKvQuery implements ReadTsKvQuery { this(key, startTs, endTs, interval, limit, aggregation, "DESC"); } - public BaseReadTsKvQuery(String key, long startTs, long endTs, long interval, int limit, Aggregation aggregation, - String order) { + public BaseReadTsKvQuery(String key, long startTs, long endTs, long interval, int limit, Aggregation aggregation, String order) { super(key, startTs, endTs); this.interval = interval; this.limit = limit; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java index 4102b30a6f..245af53d55 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java @@ -20,11 +20,16 @@ import lombok.Data; @Data public class BaseTsKvQuery implements TsKvQuery { + private static final ThreadLocal idSeq = ThreadLocal.withInitial(() -> 0); + + private final int id; private final String key; private final long startTs; private final long endTs; public BaseTsKvQuery(String key, long startTs, long endTs) { + this.id = idSeq.get(); + idSeq.set(id + 1); this.key = key; this.startTs = startTs; this.endTs = endTs; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java index 6fc32e627b..e5a7ac65e0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java @@ -20,7 +20,7 @@ import java.util.Optional; public class BasicTsKvEntry implements TsKvEntry { private static final int MAX_CHARS_PER_DATA_POINT = 512; - private final long ts; + protected final long ts; private final KvEntry kv; public BasicTsKvEntry(long ts, KvEntry kv) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/ReadTsKvQueryResult.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/ReadTsKvQueryResult.java new file mode 100644 index 0000000000..9c7a2f1754 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/ReadTsKvQueryResult.java @@ -0,0 +1,55 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +import lombok.Data; +import org.thingsboard.server.common.data.query.TsValue; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class ReadTsKvQueryResult { + + private final int queryId; + // Holds the data list; + private final List data; + // Holds the max ts of the records that match aggregation intervals (not the ts of the aggregation window, but the ts of the last record among all the intervals) + private final long lastEntryTs; + + public TsValue[] toTsValues() { + if (data != null && !data.isEmpty()) { + List queryValues = new ArrayList<>(); + for (TsKvEntry v : data) { + queryValues.add(v.toTsValue()); // TODO: add count here. + } + return queryValues.toArray(new TsValue[queryValues.size()]); + } else { + return new TsValue[0]; + } + } + + public TsValue toTsValue() { + if (data == null || data.isEmpty()) { + return TsValue.EMPTY; + } + if (data.size() > 1) { + throw new RuntimeException("Query Result has multiple data points!"); + } + return data.get(0).toTsValue(); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntry.java index 20d25d5399..06ac0d84a3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntry.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntry.java @@ -16,10 +16,11 @@ package org.thingsboard.server.common.data.kv; import com.fasterxml.jackson.annotation.JsonIgnore; +import org.thingsboard.server.common.data.query.TsValue; /** * Represents time series KV data entry - * + * * @author ashvayka * */ @@ -30,4 +31,9 @@ public interface TsKvEntry extends KvEntry { @JsonIgnore int getDataPoints(); + @JsonIgnore + default TsValue toTsValue() { + return new TsValue(getTs(), getValueAsString()); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntryAggWrapper.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntryAggWrapper.java new file mode 100644 index 0000000000..2825008485 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntryAggWrapper.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +import lombok.Data; + +@Data +public class TsKvEntryAggWrapper { + + private final TsKvEntry entry; + private final long lastEntryTs; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java index cf01c322a5..78bf01c0e9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java @@ -17,6 +17,8 @@ package org.thingsboard.server.common.data.kv; public interface TsKvQuery { + int getId(); + String getKey(); long getStartTs(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/ComparisonTsValue.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/ComparisonTsValue.java new file mode 100644 index 0000000000..301fbdb0ee --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/ComparisonTsValue.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.query; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ComparisonTsValue { + + private TsValue current; + private TsValue previous; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityData.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityData.java index c0cc778280..2b24866ac4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityData.java @@ -16,15 +16,21 @@ package org.thingsboard.server.common.data.query; import lombok.Data; +import lombok.RequiredArgsConstructor; import org.thingsboard.server.common.data.id.EntityId; import java.util.Map; @Data +@RequiredArgsConstructor public class EntityData { private final EntityId entityId; private final Map> latest; private final Map timeseries; + private final Map aggLatest; + public EntityData(EntityId entityId, Map> latest, Map timeseries) { + this(entityId, latest, timeseries, null); + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityKey.java index 5b4b5cd257..6aacc90060 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityKey.java @@ -23,6 +23,8 @@ import java.io.Serializable; @ApiModel @Data public class EntityKey implements Serializable { + private static final long serialVersionUID = -6421575477523085543L; + private final EntityKeyType type; private final String key; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/TsValue.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/TsValue.java index a086f1c21f..9dbfdfbb28 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/TsValue.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/TsValue.java @@ -15,11 +15,23 @@ */ package org.thingsboard.server.common.data.query; +import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; +import lombok.RequiredArgsConstructor; @Data +@RequiredArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) public class TsValue { + public static final TsValue EMPTY = new TsValue(0, ""); + private final long ts; private final String value; + private final Long count; + + public TsValue(long ts, String value) { + this(ts, value, null); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityLoadError.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityLoadError.java index cbfe9f6457..1f54a7c262 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityLoadError.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityLoadError.java @@ -19,7 +19,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Builder; import lombok.Data; import org.apache.commons.lang3.ClassUtils; -import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.EntityId; import java.io.Serializable; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java index 44fe42512b..c184b70ed4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java @@ -19,15 +19,19 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.kv.DataType; +import java.io.Serializable; import java.util.regex.Pattern; @Data @AllArgsConstructor @NoArgsConstructor -public class SnmpMapping { +public class SnmpMapping implements Serializable { + + private static final long serialVersionUID = 2042438869374145944L; + private String oid; private String key; private DataType dataType; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java index 22678620cf..6bb8c66bf2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.transport.snmp.config.impl.SharedAttri import org.thingsboard.server.common.data.transport.snmp.config.impl.TelemetryQueryingSnmpCommunicationConfig; import org.thingsboard.server.common.data.transport.snmp.config.impl.ToDeviceRpcRequestSnmpCommunicationConfig; +import java.io.Serializable; import java.util.List; @JsonIgnoreProperties(ignoreUnknown = true) @@ -38,7 +39,7 @@ import java.util.List; @Type(value = SharedAttributesSettingSnmpCommunicationConfig.class, name = "SHARED_ATTRIBUTES_SETTING"), @Type(value = ToDeviceRpcRequestSnmpCommunicationConfig.class, name = "TO_DEVICE_RPC_REQUEST") }) -public interface SnmpCommunicationConfig { +public interface SnmpCommunicationConfig extends Serializable { SnmpCommunicationSpec getSpec(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ClientAttributesQueryingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ClientAttributesQueryingSnmpCommunicationConfig.java index 2eec1e336c..2601e9accc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ClientAttributesQueryingSnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ClientAttributesQueryingSnmpCommunicationConfig.java @@ -20,6 +20,8 @@ import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryin public class ClientAttributesQueryingSnmpCommunicationConfig extends RepeatingQueryingSnmpCommunicationConfig { + private static final long serialVersionUID = 536740834893462914L; + @Override public SnmpCommunicationSpec getSpec() { return SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/SharedAttributesSettingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/SharedAttributesSettingSnmpCommunicationConfig.java index c034b6c26e..b80aaf3583 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/SharedAttributesSettingSnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/SharedAttributesSettingSnmpCommunicationConfig.java @@ -21,6 +21,8 @@ import org.thingsboard.server.common.data.transport.snmp.config.MultipleMappings public class SharedAttributesSettingSnmpCommunicationConfig extends MultipleMappingsSnmpCommunicationConfig { + private static final long serialVersionUID = 8981224974190924703L; + @Override public SnmpCommunicationSpec getSpec() { return SnmpCommunicationSpec.SHARED_ATTRIBUTES_SETTING; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/TelemetryQueryingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/TelemetryQueryingSnmpCommunicationConfig.java index d4b16e9e0b..4b59dbd5e4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/TelemetryQueryingSnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/TelemetryQueryingSnmpCommunicationConfig.java @@ -24,6 +24,8 @@ import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryin @Data public class TelemetryQueryingSnmpCommunicationConfig extends RepeatingQueryingSnmpCommunicationConfig { + private static final long serialVersionUID = -1367743866881596885L; + @Override public SnmpCommunicationSpec getSpec() { return SnmpCommunicationSpec.TELEMETRY_QUERYING; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcRequestSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcRequestSnmpCommunicationConfig.java index 2a587f4243..c86c330cae 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcRequestSnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcRequestSnmpCommunicationConfig.java @@ -19,6 +19,9 @@ import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; import org.thingsboard.server.common.data.transport.snmp.config.MultipleMappingsSnmpCommunicationConfig; public class ToDeviceRpcRequestSnmpCommunicationConfig extends MultipleMappingsSnmpCommunicationConfig { + + private static final long serialVersionUID = -8607312598073845460L; + @Override public SnmpCommunicationSpec getSpec() { return SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST; diff --git a/common/edge-api/pom.xml b/common/edge-api/pom.xml index aa4a095c99..576f2aeced 100644 --- a/common/edge-api/pom.xml +++ b/common/edge-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT common org.thingsboard.common diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto index f7ecbfcb8d..7272e6f96a 100644 --- a/common/edge-api/src/main/proto/edge.proto +++ b/common/edge-api/src/main/proto/edge.proto @@ -200,6 +200,7 @@ message DeviceUpdateMsg { optional string conflictName = 12; optional int64 firmwareIdMSB = 13; optional int64 firmwareIdLSB = 14; + optional bytes deviceDataBytes = 15; } message DeviceProfileUpdateMsg { diff --git a/common/message/pom.xml b/common/message/pom.xml index ab4d73f3ee..de18be2e21 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT common org.thingsboard.common diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index f8eb8c663e..55b9f4dbd5 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -22,7 +22,7 @@ import lombok.AccessLevel; import lombok.Data; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; diff --git a/common/pom.xml b/common/pom.xml index 33abef1062..770cb402d2 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT thingsboard common diff --git a/common/queue/pom.xml b/common/queue/pom.xml index e116645618..c246ab358f 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT common org.thingsboard.common diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java index a3744c3946..0a4a7e6acd 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java @@ -21,7 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TbTransportService; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo; @@ -60,7 +60,7 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider { try { serviceId = InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { - serviceId = org.apache.commons.lang3.RandomStringUtils.randomAlphabetic(10); + serviceId = StringUtils.randomAlphabetic(10); } } log.info("Current Service ID: {}", serviceId); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerStatsService.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerStatsService.java index e62ee850fa..e3177ab0b6 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerStatsService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerStatsService.java @@ -29,8 +29,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.queue.discovery.PartitionService; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java index d4946581e6..29aadcafd6 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java @@ -23,7 +23,7 @@ import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.header.Header; import org.apache.kafka.common.header.internals.RecordHeader; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueCallback; diff --git a/common/stats/pom.xml b/common/stats/pom.xml index 7648eb911a..d316980e97 100644 --- a/common/stats/pom.xml +++ b/common/stats/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT common org.thingsboard.common @@ -86,6 +86,10 @@ awaitility test + + org.thingsboard.common + data + diff --git a/common/stats/src/main/java/org/thingsboard/server/common/stats/DefaultStatsFactory.java b/common/stats/src/main/java/org/thingsboard/server/common/stats/DefaultStatsFactory.java index e71ac22d00..dff0212e99 100644 --- a/common/stats/src/main/java/org/thingsboard/server/common/stats/DefaultStatsFactory.java +++ b/common/stats/src/main/java/org/thingsboard/server/common/stats/DefaultStatsFactory.java @@ -19,7 +19,7 @@ import io.micrometer.core.instrument.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import javax.annotation.PostConstruct; import java.util.concurrent.atomic.AtomicInteger; diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index 6248f11a6c..70b2470dc4 100644 --- a/common/transport/coap/pom.xml +++ b/common/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapAdaptorUtils.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapAdaptorUtils.java index a23ca82234..1043cecc41 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapAdaptorUtils.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapAdaptorUtils.java @@ -16,7 +16,7 @@ package org.thingsboard.server.transport.coap.adaptors; import org.eclipse.californium.core.coap.Request; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.gen.transport.TransportProtos; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java index 3d4a4ee053..3568624289 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java @@ -27,7 +27,7 @@ import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.JsonConverter; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/ProtoCoapAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/ProtoCoapAdaptor.java index 1bd1ffb827..13c783675e 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/ProtoCoapAdaptor.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/ProtoCoapAdaptor.java @@ -26,7 +26,7 @@ import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.JsonConverter; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java index 1b3c936130..f8336025d4 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java @@ -23,6 +23,7 @@ import org.eclipse.californium.core.observe.ObserveRelation; import org.eclipse.californium.core.server.resources.CoapExchange; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.context.annotation.Lazy; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.thingsboard.server.coapserver.CoapServerContext; import org.thingsboard.server.common.data.DataConstants; @@ -45,6 +46,8 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.transport.DeviceDeletedEvent; +import org.thingsboard.server.common.transport.DeviceUpdatedEvent; import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.TransportDeviceProfileCache; import org.thingsboard.server.common.transport.TransportService; @@ -52,6 +55,7 @@ import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.common.transport.DeviceProfileUpdatedEvent; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.transport.coap.CoapTransportContext; @@ -97,6 +101,45 @@ public class DefaultCoapClientContext implements CoapClientContext { this.partitionService = partitionService; } + @EventListener(DeviceProfileUpdatedEvent.class) + public void onApplicationEvent(DeviceProfileUpdatedEvent event) { + var deviceProfile = event.getDeviceProfile(); + clients.values().stream().filter(state -> state.getSession() == null).forEach(state -> { + state.lock(); + try { + if (deviceProfile.getId().equals(state.getProfileId())) { + initStateAdaptor(deviceProfile, state); + } + } catch (AdaptorException e) { + log.trace("[{}] Failed to update client state due to: ", state.getDeviceId(), e); + } finally { + state.unlock(); + } + }); + } + + @EventListener(DeviceUpdatedEvent.class) + public void onApplicationEvent(DeviceUpdatedEvent event) { + var device = event.getDevice(); + var state = clients.get(device.getId()); + if (state == null) { + return; + } + state.lock(); + try { + if (state.getSession() == null) { + clients.remove(device.getId()); + } + } finally { + state.unlock(); + } + } + + @EventListener(DeviceDeletedEvent.class) + public void onApplicationEvent(DeviceDeletedEvent event) { + clients.remove(event.getDeviceId()); + } + @Override public boolean registerAttributeObservation(TbCoapClientState clientState, String token, CoapExchange exchange) { return registerFeatureObservation(clientState, token, exchange, FeatureType.ATTRIBUTES); diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml index a55dea2f2b..c0bb5d9001 100644 --- a/common/transport/http/pom.xml +++ b/common/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java index de9619cb9f..17bc257ccb 100644 --- a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java +++ b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java @@ -28,7 +28,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; diff --git a/common/transport/lwm2m/pom.xml b/common/transport/lwm2m/pom.xml index 29e74a7931..d9f92d685d 100644 --- a/common/transport/lwm2m/pom.xml +++ b/common/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java index 63221f36ff..966404cce2 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java @@ -37,6 +37,8 @@ import java.util.List; @Data public class LwM2MBootstrapConfig implements Serializable { + private static final long serialVersionUID = -4729088085817468640L; + List serverConfiguration; /** -bootstrapServer, lwm2mServer diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java index ca001e3b83..43c40280a8 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java @@ -18,7 +18,7 @@ package org.thingsboard.server.transport.lwm2m.utils; import com.fasterxml.jackson.core.type.TypeReference; import com.google.gson.JsonElement; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.eclipse.leshan.core.attributes.Attribute; import org.eclipse.leshan.core.attributes.AttributeSet; import org.eclipse.leshan.core.model.LwM2mModel; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java index c4057be1fe..cd2bb87fc4 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java @@ -23,7 +23,7 @@ import org.eclipse.leshan.core.node.ObjectLink; import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.node.codec.LwM2mValueConverter; import org.eclipse.leshan.core.util.Hex; -import org.eclipse.leshan.core.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import java.math.BigInteger; import java.text.DateFormat; diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index 2542e5914e..9906b39ba7 100644 --- a/common/transport/mqtt/pom.xml +++ b/common/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index 8408c76a16..98aaca7f06 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -24,7 +24,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.transport.TransportService; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index f37715efc8..ad07c9f9a4 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -41,7 +41,7 @@ import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java index 75c76c0cdb..b8e120f301 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java @@ -27,7 +27,7 @@ import io.netty.handler.codec.mqtt.MqttPublishMessage; import io.netty.handler.codec.mqtt.MqttPublishVariableHeader; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.transport.adaptor.AdaptorException; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java index 175bf17c02..17ae0d75b5 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java @@ -25,7 +25,7 @@ import io.netty.handler.codec.mqtt.MqttMessage; import io.netty.handler.codec.mqtt.MqttPublishMessage; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.transport.adaptor.AdaptorException; diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java index d91935086b..fad67470ba 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java @@ -35,7 +35,7 @@ import io.netty.handler.codec.mqtt.MqttPublishMessage; import lombok.extern.slf4j.Slf4j; import org.springframework.util.CollectionUtils; import org.springframework.util.ConcurrentReferenceHashMap; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; diff --git a/common/transport/pom.xml b/common/transport/pom.xml index be0da470c3..125139c523 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/snmp/pom.xml b/common/transport/snmp/pom.xml index 7bcf4dc137..c9611333f8 100644 --- a/common/transport/snmp/pom.xml +++ b/common/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.common - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT transport diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index 308768231e..932629bb12 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/DeviceDeletedEvent.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/DeviceDeletedEvent.java new file mode 100644 index 0000000000..9a7cdbefdd --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/DeviceDeletedEvent.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport; + +import lombok.Getter; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.queue.discovery.event.TbApplicationEvent; + +public final class DeviceDeletedEvent extends TbApplicationEvent { + + private static final long serialVersionUID = -7453664970966733857L; + @Getter + private final DeviceId deviceId; + + public DeviceDeletedEvent(DeviceId deviceId) { + super(new Object()); + this.deviceId = deviceId; + } +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/DeviceProfileUpdatedEvent.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/DeviceProfileUpdatedEvent.java new file mode 100644 index 0000000000..ca95a636d3 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/DeviceProfileUpdatedEvent.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport; + +import lombok.Getter; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.queue.discovery.event.TbApplicationEvent; + +public final class DeviceProfileUpdatedEvent extends TbApplicationEvent { + + @Getter + private final DeviceProfile deviceProfile; + + public DeviceProfileUpdatedEvent(DeviceProfile deviceProfile) { + super(new Object()); + this.deviceProfile = deviceProfile; + } +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 636b9554a5..3ba33d58da 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -23,7 +23,7 @@ import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; import org.apache.commons.lang3.math.NumberUtils; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java index 137ceff337..6fe0f29cfc 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java @@ -25,7 +25,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.util.JsonFormat; import lombok.extern.slf4j.Slf4j; import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.gen.transport.TransportApiProtos; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java index e3170797bd..92084db5af 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java @@ -18,7 +18,7 @@ package org.thingsboard.server.common.transport.limits; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.DeviceId; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index a63e4a01a2..8b532ae92e 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -44,7 +44,6 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; @@ -58,6 +57,8 @@ import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import org.thingsboard.server.common.stats.MessagesStats; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsType; +import org.thingsboard.server.common.transport.DeviceDeletedEvent; +import org.thingsboard.server.common.transport.DeviceProfileUpdatedEvent; import org.thingsboard.server.common.transport.DeviceUpdatedEvent; import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.TransportDeviceProfileCache; @@ -925,10 +926,7 @@ public class DefaultTransportService implements TransportService { } } else if (EntityType.DEVICE.equals(entityType)) { Optional deviceOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray()); - deviceOpt.ifPresent(device -> { - onDeviceUpdate(device); - eventPublisher.publishEvent(new DeviceUpdatedEvent(device)); - }); + deviceOpt.ifPresent(this::onDeviceUpdate); } } else if (toSessionMsg.hasEntityDeleteMsg()) { TransportProtos.EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); @@ -994,6 +992,8 @@ public class DefaultTransportService implements TransportService { transportCallbackExecutor.submit(() -> md.getListener().onDeviceProfileUpdate(newSessionInfo, deviceProfile)); } }); + + eventPublisher.publishEvent(new DeviceProfileUpdatedEvent(deviceProfile)); } private void onDeviceUpdate(Device device) { @@ -1027,6 +1027,8 @@ public class DefaultTransportService implements TransportService { transportCallbackExecutor.submit(() -> md.getListener().onDeviceUpdate(newSessionInfo, device, Optional.ofNullable(newDeviceProfile))); } }); + + eventPublisher.publishEvent(new DeviceUpdatedEvent(device)); } private void onDeviceDeleted(DeviceId deviceId) { @@ -1038,6 +1040,8 @@ public class DefaultTransportService implements TransportService { }); } }); + + eventPublisher.publishEvent(new DeviceDeletedEvent(deviceId)); } protected UUID toSessionId(TransportProtos.SessionInfoProto sessionInfo) { diff --git a/common/util/pom.xml b/common/util/pom.xml index cd45ff96d7..5717362ff0 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT common org.thingsboard.common diff --git a/common/version-control/pom.xml b/common/version-control/pom.xml index ac472c8e3c..7929a49a3b 100644 --- a/common/version-control/pom.xml +++ b/common/version-control/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT common org.thingsboard.common diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java index d3615f7228..64f4e90cb8 100644 --- a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java @@ -18,7 +18,7 @@ package org.thingsboard.server.service.sync.vc; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.eclipse.jgit.api.errors.GitAPIException; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java index 7d1710a87b..d820659e77 100644 --- a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java @@ -20,7 +20,7 @@ import com.google.common.collect.Ordering; import com.google.common.collect.Streams; import lombok.Data; import lombok.Getter; -import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.apache.sshd.common.util.security.SecurityUtils; import org.eclipse.jgit.api.CloneCommand; import org.eclipse.jgit.api.Git; diff --git a/dao/pom.xml b/dao/pom.xml index ae98928787..f911a7e043 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT thingsboard dao diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 714e131025..f83a66fce9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -24,6 +24,7 @@ import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.alarm.Alarm; @@ -142,6 +143,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } @Override + @Transactional public AlarmOperationResult deleteAlarm(TenantId tenantId, AlarmId alarmId) { try { log.debug("Deleting Alarm Id: {}", alarmId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java index 31c40e0d96..3ffcc999c3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java @@ -20,10 +20,9 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; -import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; @@ -150,6 +149,7 @@ public class BaseAssetService extends AbstractCachedEntityService { @Transient protected String strKey; + @Transient + protected Long aggValuesLastTs; + @Transient + protected Long aggValuesCount; + + public AbstractTsKvEntity() { + } + + public AbstractTsKvEntity(Long aggValuesLastTs) { + this.aggValuesLastTs = aggValuesLastTs; + } + public abstract boolean isNotEmpty(); protected static boolean isAllNull(Object... args) { @@ -105,7 +118,12 @@ public abstract class AbstractTsKvEntity implements ToData { } else if (jsonValue != null) { kvEntry = new JsonDataEntry(strKey, jsonValue); } - return new BasicTsKvEntry(ts, kvEntry); + + if (aggValuesCount == null) { + return new BasicTsKvEntry(ts, kvEntry); + } else { + return new AggTsKvEntry(ts, kvEntry, aggValuesCount); + } } } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java index ccaea5524e..1e39760a12 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java @@ -24,9 +24,9 @@ import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; -import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.ShortCustomerInfo; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java index de5dd6a22c..d7a7e6e055 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java @@ -21,9 +21,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.ShortCustomerInfo; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java index 68b5baa9bf..8aed530950 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java @@ -16,11 +16,11 @@ package org.thingsboard.server.dao.model.sql; import com.fasterxml.jackson.databind.JsonNode; -import io.micrometer.core.instrument.util.StringUtils; import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.OAuth2ParamsId; import org.thingsboard.server.common.data.id.OAuth2RegistrationId; import org.thingsboard.server.common.data.oauth2.MapperType; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvEntity.java index 05c264a48b..ba5ad92f4f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvEntity.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.model.sqlts.timescale.ts; import lombok.Data; import lombok.EqualsAndHashCode; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import javax.persistence.ColumnResult; @@ -62,6 +62,7 @@ import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.F @ColumnResult(name = "doubleCountValue", type = Long.class), @ColumnResult(name = "strValue", type = String.class), @ColumnResult(name = "aggType", type = String.class), + @ColumnResult(name = "maxAggTs", type = Long.class), } ), }), @@ -78,6 +79,7 @@ import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.F @ColumnResult(name = "longValueCount", type = Long.class), @ColumnResult(name = "doubleValueCount", type = Long.class), @ColumnResult(name = "jsonValueCount", type = Long.class), + @ColumnResult(name = "maxAggTs", type = Long.class), } ) }), @@ -114,7 +116,8 @@ public final class TimescaleTsKvEntity extends AbstractTsKvEntity { public TimescaleTsKvEntity() { } - public TimescaleTsKvEntity(Long tsBucket, Long interval, Long longValue, Double doubleValue, Long longCountValue, Long doubleCountValue, String strValue, String aggType) { + public TimescaleTsKvEntity(Long tsBucket, Long interval, Long longValue, Double doubleValue, Long longCountValue, Long doubleCountValue, String strValue, String aggType, Long aggValuesLastTs) { + super(aggValuesLastTs); if (!StringUtils.isEmpty(strValue)) { this.strValue = strValue; } @@ -135,6 +138,7 @@ public final class TimescaleTsKvEntity extends AbstractTsKvEntity { } else { this.doubleValue = 0.0; } + this.aggValuesCount = totalCount; break; case SUM: if (doubleCountValue > 0) { @@ -157,7 +161,8 @@ public final class TimescaleTsKvEntity extends AbstractTsKvEntity { } } - public TimescaleTsKvEntity(Long tsBucket, Long interval, Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount, Long jsonValueCount) { + public TimescaleTsKvEntity(Long tsBucket, Long interval, Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount, Long jsonValueCount, Long aggValuesLastTs) { + super(aggValuesLastTs); if (!isAllNull(tsBucket, interval, booleanValueCount, strValueCount, longValueCount, doubleValueCount, jsonValueCount)) { this.ts = tsBucket + interval / 2; if (booleanValueCount != 0) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java index 1c0277f8b6..15fd30c742 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java @@ -16,12 +16,15 @@ package org.thingsboard.server.dao.model.sqlts.ts; import lombok.Data; +import lombok.EqualsAndHashCode; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import javax.persistence.Entity; import javax.persistence.IdClass; import javax.persistence.Table; +import javax.persistence.Transient; +@EqualsAndHashCode(callSuper = true) @Data @Entity @Table(name = "ts_kv") @@ -31,11 +34,13 @@ public final class TsKvEntity extends AbstractTsKvEntity { public TsKvEntity() { } - public TsKvEntity(String strValue) { + public TsKvEntity(String strValue, Long aggValuesLastTs) { + super(aggValuesLastTs); this.strValue = strValue; } - public TsKvEntity(Long longValue, Double doubleValue, Long longCountValue, Long doubleCountValue, String aggType) { + public TsKvEntity(Long longValue, Double doubleValue, Long longCountValue, Long doubleCountValue, String aggType, Long aggValuesLastTs) { + super(aggValuesLastTs); if (!isAllNull(longValue, doubleValue, longCountValue, doubleCountValue)) { switch (aggType) { case AVG: @@ -52,6 +57,7 @@ public final class TsKvEntity extends AbstractTsKvEntity { } else { this.doubleValue = 0.0; } + this.aggValuesCount = totalCount; break; case SUM: if (doubleCountValue > 0) { @@ -74,7 +80,8 @@ public final class TsKvEntity extends AbstractTsKvEntity { } } - public TsKvEntity(Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount, Long jsonValueCount) { + public TsKvEntity(Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount, Long jsonValueCount, Long aggValuesLastTs) { + super(aggValuesLastTs); if (!isAllNull(booleanValueCount, strValueCount, longValueCount, doubleValueCount)) { if (booleanValueCount != 0) { this.longValue = booleanValueCount; diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index fd4b29817b..b1f5d4e397 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -19,8 +19,8 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.MapperType; import org.thingsboard.server.common.data.oauth2.OAuth2BasicMapperConfig; diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index f60920a31d..6f6a942162 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -28,8 +28,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; import org.springframework.transaction.support.TransactionSynchronizationManager; -import org.springframework.util.StringUtils; import org.thingsboard.server.cache.TbTransactionalCache; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.relation.EntityRelation; diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index b511ab3da2..420739e37c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -131,7 +131,6 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } @Override - @Transactional public RuleChainUpdateResult saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData) { Validator.validateId(ruleChainMetaData.getRuleChainId(), "Incorrect rule chain id."); RuleChain ruleChain = findRuleChainById(tenantId, ruleChainMetaData.getRuleChainId()); @@ -748,6 +747,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } @Override + @Transactional public void deleteRuleNodes(TenantId tenantId, RuleChainId ruleChainId) { List nodeRelations = getRuleChainToNodeRelations(tenantId, ruleChainId); for (EntityRelation relation : nodeRelations) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java index 81297b3594..9bf729ad25 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java @@ -17,9 +17,9 @@ package org.thingsboard.server.dao.service; import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.TenantEntityDao; import org.thingsboard.server.dao.TenantEntityWithDataDao; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java index 8033688787..82cb8084fd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java @@ -16,9 +16,9 @@ package org.thingsboard.server.dao.service.validator; import lombok.AllArgsConstructor; -import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java index a95f7a43aa..f9097cfcc0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.service.validator; import lombok.AllArgsConstructor; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.exception.DataValidationException; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java index f61cf69845..b7685a0476 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java @@ -18,9 +18,9 @@ package org.thingsboard.server.dao.service.validator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java index b5d0247e28..2023ec353f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.service.validator; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.dao.exception.DataValidationException; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ComponentDescriptorDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ComponentDescriptorDataValidator.java index f7eceec4d5..0d2caed6d4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ComponentDescriptorDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ComponentDescriptorDataValidator.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.dao.service.validator; -import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.dao.exception.DataValidationException; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java index 9cc8d6a0de..83ff93e079 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java @@ -15,12 +15,12 @@ */ package org.thingsboard.server.dao.service.validator; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.customer.CustomerDao; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java index 23b960253c..a3a4ef50fd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java @@ -15,12 +15,12 @@ */ package org.thingsboard.server.dao.service.validator; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.dashboard.DashboardDao; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java index d30180a892..a9d8e27f80 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java @@ -18,10 +18,10 @@ package org.thingsboard.server.dao.service.validator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java index b88724f7af..3f0d3659ce 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java @@ -17,8 +17,8 @@ package org.thingsboard.server.dao.service.validator; import lombok.AllArgsConstructor; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; @@ -49,13 +49,13 @@ public class EdgeDataValidator extends DataValidator { @Override protected void validateDataImpl(TenantId tenantId, Edge edge) { - if (org.springframework.util.StringUtils.isEmpty(edge.getType())) { + if (StringUtils.isEmpty(edge.getType())) { throw new DataValidationException("Edge type should be specified!"); } - if (org.springframework.util.StringUtils.isEmpty(edge.getName())) { + if (StringUtils.isEmpty(edge.getName())) { throw new DataValidationException("Edge name should be specified!"); } - if (org.springframework.util.StringUtils.isEmpty(edge.getSecret())) { + if (StringUtils.isEmpty(edge.getSecret())) { throw new DataValidationException("Edge secret should be specified!"); } if (StringUtils.isEmpty(edge.getRoutingKey())) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java index 7532dda195..e8220b71ba 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java @@ -16,10 +16,10 @@ package org.thingsboard.server.dao.service.validator; import lombok.AllArgsConstructor; -import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.customer.CustomerDao; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EventDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EventDataValidator.java index 8ad541ebac..93a0e6416e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EventDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EventDataValidator.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.dao.service.validator; -import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.event.Event; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.exception.DataValidationException; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java index 49de43dba3..03f1ad59e1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java @@ -15,10 +15,10 @@ */ package org.thingsboard.server.dao.service.validator; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java index 1581a61b80..e6ae7d04a8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java @@ -15,11 +15,11 @@ */ package org.thingsboard.server.dao.service.validator; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainType; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantDataValidator.java index 65770aefdb..cea8bc9fb7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantDataValidator.java @@ -15,9 +15,9 @@ */ package org.thingsboard.server.dao.service.validator; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.exception.DataValidationException; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java index 8484ecfa59..0368e38173 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java @@ -15,10 +15,10 @@ */ package org.thingsboard.server.dao.service.validator; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.queue.ProcessingStrategy; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserCredentialsDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserCredentialsDataValidator.java index 5920ad552f..b15f0d2daf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserCredentialsDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserCredentialsDataValidator.java @@ -15,10 +15,10 @@ */ package org.thingsboard.server.dao.service.validator; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.UserCredentials; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java index 21e2143f1a..4df37ddc00 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java @@ -15,12 +15,12 @@ */ package org.thingsboard.server.dao.service.validator; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java index 25c6b5db86..aad1d1bc4f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java @@ -16,9 +16,8 @@ package org.thingsboard.server.dao.service.validator; import lombok.AllArgsConstructor; -import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; @@ -26,7 +25,6 @@ import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.service.DataValidator; -import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.widget.WidgetTypeDao; import org.thingsboard.server.dao.widget.WidgetsBundleDao; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java index b950a12826..14faf3e80c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java @@ -16,8 +16,8 @@ package org.thingsboard.server.dao.service.validator; import lombok.AllArgsConstructor; -import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.exception.DataValidationException; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index 2e3fe80448..62516232d2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java @@ -23,13 +23,13 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceIdInfo; import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.DeviceIdInfo; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.ota.OtaPackageType; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java index 07a680493f..6f4cd8587a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao.sql.device; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; @@ -24,6 +23,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java index 36f834b8bc..2f05f66cdf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java @@ -18,11 +18,11 @@ package org.thingsboard.server.dao.sql.edge; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.EdgeEventId; import org.thingsboard.server.common.data.id.EdgeId; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java index 504bc91911..a9a3e25db2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java @@ -24,13 +24,13 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.event.RuleChainDebugEventFilter; -import org.thingsboard.server.common.data.event.RuleNodeDebugEventFilter; import org.thingsboard.server.common.data.event.ErrorEventFilter; import org.thingsboard.server.common.data.event.Event; import org.thingsboard.server.common.data.event.EventFilter; import org.thingsboard.server.common.data.event.EventType; import org.thingsboard.server.common.data.event.LifeCycleEventFilter; +import org.thingsboard.server.common.data.event.RuleChainDebugEventFilter; +import org.thingsboard.server.common.data.event.RuleNodeDebugEventFilter; import org.thingsboard.server.common.data.event.StatisticsEventFilter; import org.thingsboard.server.common.data.id.EventId; import org.thingsboard.server.common.data.page.PageData; @@ -38,7 +38,6 @@ import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.event.EventDao; -import org.thingsboard.server.dao.model.sql.AssetInfoEntity; import org.thingsboard.server.dao.model.sql.EventEntity; import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java index 7d660519eb..ff156eb51c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java @@ -18,8 +18,8 @@ package org.thingsboard.server.dao.sql.query; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index a1be8884ff..69cb4a2d86 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java @@ -16,11 +16,11 @@ package org.thingsboard.server.dao.sql.query; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Repository; import org.springframework.transaction.support.TransactionTemplate; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index dde5f35f28..bc7940f300 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -17,12 +17,12 @@ package org.thingsboard.server.dao.sql.query; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Repository; import org.springframework.transaction.support.TransactionTemplate; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityDataAdapter.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityDataAdapter.java index 49640c1640..e2d691f32b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityDataAdapter.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityDataAdapter.java @@ -54,8 +54,8 @@ public class EntityDataAdapter { EntityType entityType = EntityType.valueOf((String) row.get("entity_type")); EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, id); Map> latest = new HashMap<>(); - Map timeseries = new HashMap<>(); - EntityData entityData = new EntityData(entityId, latest, timeseries); + //Maybe avoid empty hashmaps? + EntityData entityData = new EntityData(entityId, latest, new HashMap<>(), new HashMap<>()); for (EntityKeyMapping mapping : selectionMapping) { if (!mapping.isIgnore()) { EntityKey entityKey = mapping.getEntityKey(); @@ -81,7 +81,10 @@ public class EntityDataAdapter { if (value != null) { String strVal = value.toString(); // check number - if (strVal.length() > 0 && NumberUtils.isParsable(strVal)) { + if (NumberUtils.isParsable(strVal)) { + if (strVal.startsWith("0") && !strVal.startsWith("0.")) { + return strVal; + } try { long longVal = Long.parseLong(strVal); return Long.toString(longVal); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index 1499b7150f..649d3e7f2c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -16,9 +16,9 @@ package org.thingsboard.server.dao.sql.query; import lombok.Data; -import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.ComplexFilterPredicate; import org.thingsboard.server.common.data.query.EntityCountQuery; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java index 69f16d4bbc..22c4661fcd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java @@ -17,8 +17,6 @@ package org.thingsboard.server.dao.sqlts; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.SettableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; @@ -26,9 +24,9 @@ import org.springframework.data.domain.Sort; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.Aggregation; -import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.DaoUtil; @@ -47,10 +45,9 @@ import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.CompletableFuture; import java.util.function.Function; -import java.util.stream.Collectors; +@SuppressWarnings("UnstableApiUsage") @Slf4j public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSqlTimeseriesDao implements TimeseriesDao { @@ -81,7 +78,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq Comparator.comparing((Function) AbstractTsKvEntity::getEntityId) .thenComparing(AbstractTsKvEntity::getKey) .thenComparing(AbstractTsKvEntity::getTs) - ); + ); } @PreDestroy @@ -114,32 +111,32 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq } @Override - public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { + public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { return processFindAllAsync(tenantId, entityId, queries); } @Override - public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { + public ListenableFuture findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { if (query.getAggregation() == Aggregation.NONE) { - return findAllAsyncWithLimit(entityId, query); + return Futures.immediateFuture(findAllAsyncWithLimit(entityId, query)); } else { - List>> futures = new ArrayList<>(); - long endPeriod = query.getEndTs(); + List>> futures = new ArrayList<>(); long startPeriod = query.getStartTs(); + long endPeriod = Math.max(query.getStartTs() + 1, query.getEndTs()); long step = query.getInterval(); - while (startPeriod <= endPeriod) { + while (startPeriod < endPeriod) { long startTs = startPeriod; - long endTs = Math.min(startPeriod + step, endPeriod + 1); + long endTs = Math.min(startPeriod + step, endPeriod); long ts = startTs + (endTs - startTs) / 2; - ListenableFuture> aggregateTsKvEntry = findAndAggregateAsync(entityId, query.getKey(), startTs, endTs, ts, query.getAggregation()); + ListenableFuture> aggregateTsKvEntry = findAndAggregateAsync(entityId, query.getKey(), startTs, endTs, ts, query.getAggregation()); futures.add(aggregateTsKvEntry); startPeriod = endTs; } - return getTskvEntriesFuture(Futures.allAsList(futures)); + return getReadTsKvQueryResultFuture(query, Futures.allAsList(futures)); } } - private ListenableFuture> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { + private ReadTsKvQueryResult findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { Integer keyId = getOrSaveKeyId(query.getKey()); List tsKvEntities = tsKvRepository.findAllWithLimit( entityId.getId(), @@ -147,125 +144,52 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq query.getStartTs(), query.getEndTs(), PageRequest.of(0, query.getLimit(), - Sort.by(new Sort.Order(Sort.Direction.fromString(query.getOrder()), "ts").nullsNative()))); + Sort.by(new Sort.Order(Sort.Direction.fromString(query.getOrder()), "ts").nullsNative()))); tsKvEntities.forEach(tsKvEntity -> tsKvEntity.setStrKey(query.getKey())); - return Futures.immediateFuture(DaoUtil.convertDataList(tsKvEntities)); + List tsKvEntries = DaoUtil.convertDataList(tsKvEntities); + long lastTs = tsKvEntries.stream().map(TsKvEntry::getTs).max(Long::compare).orElse(query.getStartTs()); + return new ReadTsKvQueryResult(query.getId(), tsKvEntries, lastTs); } - ListenableFuture> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { - List> entitiesFutures = new ArrayList<>(); - switchAggregation(entityId, key, startTs, endTs, aggregation, entitiesFutures); - return Futures.transform(setFutures(entitiesFutures), entity -> { + ListenableFuture> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { + return service.submit(() -> { + TsKvEntity entity = switchAggregation(entityId, key, startTs, endTs, aggregation); if (entity != null && entity.isNotEmpty()) { entity.setEntityId(entityId.getId()); entity.setStrKey(key); entity.setTs(ts); - return Optional.of(DaoUtil.getData(entity)); + return Optional.of(entity); } else { return Optional.empty(); } - }, MoreExecutors.directExecutor()); + }); } - protected void switchAggregation(EntityId entityId, String key, long startTs, long endTs, Aggregation aggregation, List> entitiesFutures) { + protected TsKvEntity switchAggregation(EntityId entityId, String key, long startTs, long endTs, Aggregation aggregation) { + var keyId = getOrSaveKeyId(key); switch (aggregation) { case AVG: - findAvg(entityId, key, startTs, endTs, entitiesFutures); - break; + return tsKvRepository.findAvg(entityId.getId(), keyId, startTs, endTs); case MAX: - findMax(entityId, key, startTs, endTs, entitiesFutures); - break; + var max = tsKvRepository.findNumericMax(entityId.getId(), keyId, startTs, endTs); + if (max.isNotEmpty()) { + return max; + } else { + return tsKvRepository.findStringMax(entityId.getId(), keyId, startTs, endTs); + } case MIN: - findMin(entityId, key, startTs, endTs, entitiesFutures); - break; + var min = tsKvRepository.findNumericMin(entityId.getId(), keyId, startTs, endTs); + if (min.isNotEmpty()) { + return min; + } else { + return tsKvRepository.findStringMin(entityId.getId(), keyId, startTs, endTs); + } case SUM: - findSum(entityId, key, startTs, endTs, entitiesFutures); - break; + return tsKvRepository.findSum(entityId.getId(), keyId, startTs, endTs); case COUNT: - findCount(entityId, key, startTs, endTs, entitiesFutures); - break; + return tsKvRepository.findCount(entityId.getId(), keyId, startTs, endTs); default: throw new IllegalArgumentException("Not supported aggregation type: " + aggregation); } } - - protected void findCount(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { - Integer keyId = getOrSaveKeyId(key); - entitiesFutures.add(tsKvRepository.findCount( - entityId.getId(), - keyId, - startTs, - endTs)); - } - - protected void findSum(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { - Integer keyId = getOrSaveKeyId(key); - entitiesFutures.add(tsKvRepository.findSum( - entityId.getId(), - keyId, - startTs, - endTs)); - } - - protected void findMin(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { - Integer keyId = getOrSaveKeyId(key); - entitiesFutures.add(tsKvRepository.findStringMin( - entityId.getId(), - keyId, - startTs, - endTs)); - entitiesFutures.add(tsKvRepository.findNumericMin( - entityId.getId(), - keyId, - startTs, - endTs)); - } - - protected void findMax(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { - Integer keyId = getOrSaveKeyId(key); - entitiesFutures.add(tsKvRepository.findStringMax( - entityId.getId(), - keyId, - startTs, - endTs)); - entitiesFutures.add(tsKvRepository.findNumericMax( - entityId.getId(), - keyId, - startTs, - endTs)); - } - - protected void findAvg(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { - Integer keyId = getOrSaveKeyId(key); - entitiesFutures.add(tsKvRepository.findAvg( - entityId.getId(), - keyId, - startTs, - endTs)); - } - - protected SettableFuture setFutures(List> entitiesFutures) { - SettableFuture listenableFuture = SettableFuture.create(); - CompletableFuture> entities = - CompletableFuture.allOf(entitiesFutures.toArray(new CompletableFuture[entitiesFutures.size()])) - .thenApply(v -> entitiesFutures.stream() - .map(CompletableFuture::join) - .collect(Collectors.toList())); - - entities.whenComplete((tsKvEntities, throwable) -> { - if (throwable != null) { - listenableFuture.setException(throwable); - } else { - TsKvEntity result = null; - for (TsKvEntity entity : tsKvEntities) { - if (entity.isNotEmpty()) { - result = entity; - break; - } - } - listenableFuture.set(result); - } - }); - return listenableFuture; - } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java index aa0d9b1c47..3de21e41bf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java @@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Value; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; @@ -38,6 +39,7 @@ import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +@SuppressWarnings("UnstableApiUsage") @Slf4j public abstract class AbstractSqlTimeseriesDao extends BaseAbstractSqlTimeseriesDao implements AggregationTimeseriesDao { @@ -86,22 +88,19 @@ public abstract class AbstractSqlTimeseriesDao extends BaseAbstractSqlTimeseries } } - protected ListenableFuture> processFindAllAsync(TenantId tenantId, EntityId entityId, List queries) { - List>> futures = queries + protected ListenableFuture> processFindAllAsync(TenantId tenantId, EntityId entityId, List queries) { + List> futures = queries .stream() .map(query -> findAllAsync(tenantId, entityId, query)) .collect(Collectors.toList()); return Futures.transform(Futures.allAsList(futures), new Function<>() { @Nullable @Override - public List apply(@Nullable List> results) { + public List apply(@Nullable List results) { if (results == null || results.isEmpty()) { return null; } - return results.stream() - .filter(Objects::nonNull) - .flatMap(List::stream) - .collect(Collectors.toList()); + return results.stream().filter(Objects::nonNull).collect(Collectors.toList()); } }, service); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AggregationTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AggregationTimeseriesDao.java index 31270bacc7..994555aaa5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AggregationTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AggregationTimeseriesDao.java @@ -19,11 +19,12 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.common.data.kv.TsKvEntry; import java.util.List; public interface AggregationTimeseriesDao { - ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query); + ListenableFuture findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query); } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java index 82e54168bc..1946198505 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java @@ -22,14 +22,20 @@ import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; +import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; +import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; import org.thingsboard.server.dao.sqlts.dictionary.TsKvDictionaryRepository; import javax.annotation.Nullable; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -80,18 +86,20 @@ public abstract class BaseAbstractSqlTimeseriesDao extends JpaAbstractDaoListeni return keyId; } - protected ListenableFuture> getTskvEntriesFuture(ListenableFuture>> future) { - return Futures.transform(future, new Function>, List>() { + protected ListenableFuture getReadTsKvQueryResultFuture(ReadTsKvQuery query, ListenableFuture>> future) { + return Futures.transform(future, new Function<>() { @Nullable @Override - public List apply(@Nullable List> results) { + public ReadTsKvQueryResult apply(@Nullable List> results) { if (results == null || results.isEmpty()) { return null; } - return results.stream() - .filter(Optional::isPresent) - .map(Optional::get) - .collect(Collectors.toList()); + List data = results.stream().filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()); + var lastTs = data.stream().map(AbstractTsKvEntity::getAggValuesLastTs).filter(Objects::nonNull).max(Long::compare); + if (lastTs.isEmpty()) { + lastTs = data.stream().map(AbstractTsKvEntity::getTs).filter(Objects::nonNull).max(Long::compare); + } + return new ReadTsKvQueryResult(query.getId(), DaoUtil.convertDataList(data), lastTs.orElse(query.getStartTs())); } }, service); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java index db30adead2..50975f21de 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java @@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult; @@ -190,7 +191,8 @@ public class SqlTimeseriesLatestDao extends BaseAbstractSqlTimeseriesDao impleme long endTs = query.getStartTs() - 1; ReadTsKvQuery findNewLatestQuery = new BaseReadTsKvQuery(query.getKey(), startTs, endTs, endTs - startTs, 1, Aggregation.NONE, DESC_ORDER); - return aggregationTimeseriesDao.findAllAsync(tenantId, entityId, findNewLatestQuery); + return Futures.transform(aggregationTimeseriesDao.findAllAsync(tenantId, entityId, findNewLatestQuery), + ReadTsKvQueryResult::getData, MoreExecutors.directExecutor()); } protected ListenableFuture getFindLatestFuture(EntityId entityId, String key) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java index 9cf796b0aa..d37234dfde 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java @@ -36,54 +36,79 @@ public class AggregationRepository { public static final String FIND_SUM = "findSum"; public static final String FIND_COUNT = "findCount"; - public static final String FROM_WHERE_CLAUSE = "FROM ts_kv tskv WHERE tskv.entity_id = cast(:entityId AS uuid) AND tskv.key= cast(:entityKey AS int) AND tskv.ts > :startTs AND tskv.ts <= :endTs GROUP BY tskv.entity_id, tskv.key, tsBucket ORDER BY tskv.entity_id, tskv.key, tsBucket"; - - public static final String FIND_AVG_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts, :startTs) AS tsBucket, :timeBucket AS interval, SUM(COALESCE(tskv.long_v, 0)) AS longValue, SUM(COALESCE(tskv.dbl_v, 0.0)) AS doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, null AS strValue, 'AVG' AS aggType "; - - public static final String FIND_MAX_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts, :startTs) AS tsBucket, :timeBucket AS interval, MAX(COALESCE(tskv.long_v, -9223372036854775807)) AS longValue, MAX(COALESCE(tskv.dbl_v, -1.79769E+308)) as doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, MAX(tskv.str_v) AS strValue, 'MAX' AS aggType "; - - public static final String FIND_MIN_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts, :startTs) AS tsBucket, :timeBucket AS interval, MIN(COALESCE(tskv.long_v, 9223372036854775807)) AS longValue, MIN(COALESCE(tskv.dbl_v, 1.79769E+308)) as doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, MIN(tskv.str_v) AS strValue, 'MIN' AS aggType "; - - public static final String FIND_SUM_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts, :startTs) AS tsBucket, :timeBucket AS interval, SUM(COALESCE(tskv.long_v, 0)) AS longValue, SUM(COALESCE(tskv.dbl_v, 0.0)) AS doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, null AS strValue, null AS jsonValue, 'SUM' AS aggType "; - - public static final String FIND_COUNT_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts, :startTs) AS tsBucket, :timeBucket AS interval, SUM(CASE WHEN tskv.bool_v IS NULL THEN 0 ELSE 1 END) AS booleanValueCount, SUM(CASE WHEN tskv.str_v IS NULL THEN 0 ELSE 1 END) AS strValueCount, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longValueCount, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleValueCount, SUM(CASE WHEN tskv.json_v IS NULL THEN 0 ELSE 1 END) AS jsonValueCount "; + public static final String FROM_WHERE_CLAUSE = "FROM ts_kv tskv WHERE " + + "tskv.entity_id = cast(:entityId AS uuid) " + + "AND tskv.key= cast(:entityKey AS int) " + + "AND tskv.ts >= :startTs AND tskv.ts < :endTs " + + "GROUP BY tskv.entity_id, tskv.key, tsBucket " + + "ORDER BY tskv.entity_id, tskv.key, tsBucket"; + + public static final String FIND_AVG_QUERY = "SELECT " + + "time_bucket(:timeBucket, tskv.ts, :startTs) AS tsBucket, :timeBucket AS interval, " + + "SUM(COALESCE(tskv.long_v, 0)) AS longValue, " + + "SUM(COALESCE(tskv.dbl_v, 0.0)) AS doubleValue, " + + "SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, " + + "SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, " + + "null AS strValue, 'AVG' AS aggType, MAX(tskv.ts) AS maxAggTs "; + + public static final String FIND_MAX_QUERY = "SELECT " + + "time_bucket(:timeBucket, tskv.ts, :startTs) AS tsBucket, :timeBucket AS interval, " + + "MAX(COALESCE(tskv.long_v, -9223372036854775807)) AS longValue, " + + "MAX(COALESCE(tskv.dbl_v, -1.79769E+308)) as doubleValue, " + + "SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, " + + "SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, " + + "MAX(tskv.str_v) AS strValue, 'MAX' AS aggType, MAX(tskv.ts) AS maxAggTs "; + + public static final String FIND_MIN_QUERY = "SELECT " + + "time_bucket(:timeBucket, tskv.ts, :startTs) AS tsBucket, :timeBucket AS interval, " + + "MIN(COALESCE(tskv.long_v, 9223372036854775807)) AS longValue, " + + "MIN(COALESCE(tskv.dbl_v, 1.79769E+308)) as doubleValue, " + + "SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, " + + "SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, " + + "MIN(tskv.str_v) AS strValue, 'MIN' AS aggType, MAX(tskv.ts) AS maxAggTs "; + + public static final String FIND_SUM_QUERY = "SELECT " + + "time_bucket(:timeBucket, tskv.ts, :startTs) AS tsBucket, :timeBucket AS interval, " + + "SUM(COALESCE(tskv.long_v, 0)) AS longValue, SUM(COALESCE(tskv.dbl_v, 0.0)) AS doubleValue, " + + "SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, " + + "SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, " + + "null AS strValue, null AS jsonValue, 'SUM' AS aggType, MAX(tskv.ts) AS maxAggTs "; + + public static final String FIND_COUNT_QUERY = "SELECT " + + "time_bucket(:timeBucket, tskv.ts, :startTs) AS tsBucket, :timeBucket AS interval, " + + "SUM(CASE WHEN tskv.bool_v IS NULL THEN 0 ELSE 1 END) AS booleanValueCount, " + + "SUM(CASE WHEN tskv.str_v IS NULL THEN 0 ELSE 1 END) AS strValueCount, " + + "SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longValueCount, " + + "SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleValueCount, " + + "SUM(CASE WHEN tskv.json_v IS NULL THEN 0 ELSE 1 END) AS jsonValueCount, " + + "MAX(tskv.ts) AS maxAggTs "; @PersistenceContext private EntityManager entityManager; - @Async - public CompletableFuture> findAvg(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { - @SuppressWarnings("unchecked") - List resultList = getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_AVG); - return CompletableFuture.supplyAsync(() -> resultList); + @SuppressWarnings("unchecked") + public List findAvg(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { + return getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_AVG); } - @Async - public CompletableFuture> findMax(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { - @SuppressWarnings("unchecked") - List resultList = getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_MAX); - return CompletableFuture.supplyAsync(() -> resultList); + @SuppressWarnings("unchecked") + public List findMax(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { + return getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_MAX); } - @Async - public CompletableFuture> findMin(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { - @SuppressWarnings("unchecked") - List resultList = getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_MIN); - return CompletableFuture.supplyAsync(() -> resultList); + @SuppressWarnings("unchecked") + public List findMin(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { + return getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_MIN); } - @Async - public CompletableFuture> findSum(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { - @SuppressWarnings("unchecked") - List resultList = getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_SUM); - return CompletableFuture.supplyAsync(() -> resultList); + @SuppressWarnings("unchecked") + public List findSum(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { + return getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_SUM); } - @Async - public CompletableFuture> findCount(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { - @SuppressWarnings("unchecked") - List resultList = getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_COUNT); - return CompletableFuture.supplyAsync(() -> resultList); + @SuppressWarnings("unchecked") + public List findCount(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { + return getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_COUNT); } private List getResultList(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs, String query) { @@ -96,5 +121,4 @@ public class AggregationRepository { .getResultList(); } - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java index 9c03a9e3dd..6e9f9e61b8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java @@ -30,11 +30,13 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import org.thingsboard.server.dao.model.sqlts.timescale.ts.TimescaleTsKvEntity; +import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper; import org.thingsboard.server.dao.sqlts.AbstractSqlTimeseriesDao; @@ -101,13 +103,13 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements } @Override - public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { + public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { return processFindAllAsync(tenantId, entityId, queries); } @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { - int dataPointDays = getDataPointDays(tsKvEntry, computeTtl(ttl)); + int dataPointDays = getDataPointDays(tsKvEntry, computeTtl(ttl)); String strKey = tsKvEntry.getKey(); Integer keyId = getOrSaveKeyId(strKey); TimescaleTsKvEntity entity = new TimescaleTsKvEntity(); @@ -148,15 +150,15 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements } @Override - public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { + public ListenableFuture findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { if (query.getAggregation() == Aggregation.NONE) { - return findAllAsyncWithLimit(entityId, query); + return Futures.immediateFuture(findAllAsyncWithLimit(entityId, query)); } else { long startTs = query.getStartTs(); - long endTs = query.getEndTs(); + long endTs = Math.max(query.getStartTs() + 1, query.getEndTs()); long timeBucket = query.getInterval(); - ListenableFuture>> future = findAllAndAggregateAsync(entityId, query.getKey(), startTs, endTs, timeBucket, query.getAggregation()); - return getTskvEntriesFuture(future); + List> data = findAllAndAggregateAsync(entityId, query.getKey(), startTs, endTs, timeBucket, query.getAggregation()); + return getReadTsKvQueryResultFuture(query, Futures.immediateFuture(data)); } } @@ -165,7 +167,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements super.cleanup(systemTtl); } - private ListenableFuture> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { + private ReadTsKvQueryResult findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { String strKey = query.getKey(); Integer keyId = getOrSaveKeyId(strKey); List timescaleTsKvEntities = tsKvRepository.findAllWithLimit( @@ -174,105 +176,60 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements query.getStartTs(), query.getEndTs(), PageRequest.of(0, query.getLimit(), - Sort.by(new Sort.Order(Sort.Direction.fromString(query.getOrder()), "ts").nullsNative())));; + Sort.by(new Sort.Order(Sort.Direction.fromString(query.getOrder()), "ts").nullsNative()))); + ; timescaleTsKvEntities.forEach(tsKvEntity -> tsKvEntity.setStrKey(strKey)); - return Futures.immediateFuture(DaoUtil.convertDataList(timescaleTsKvEntities)); + var tsKvEntries = DaoUtil.convertDataList(timescaleTsKvEntities); + long lastTs = tsKvEntries.stream().map(TsKvEntry::getTs).max(Long::compare).orElse(query.getStartTs()); + return new ReadTsKvQueryResult(query.getId(), tsKvEntries, lastTs); } - private ListenableFuture>> findAllAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long timeBucket, Aggregation aggregation) { - CompletableFuture> listCompletableFuture = switchAggregation(key, startTs, endTs, timeBucket, aggregation, entityId.getId()); - SettableFuture> listenableFuture = SettableFuture.create(); - listCompletableFuture.whenComplete((timescaleTsKvEntities, throwable) -> { - if (throwable != null) { - listenableFuture.setException(throwable); - } else { - listenableFuture.set(timescaleTsKvEntities); - } - }); - return Futures.transform(listenableFuture, timescaleTsKvEntities -> { - if (!CollectionUtils.isEmpty(timescaleTsKvEntities)) { - List> result = new ArrayList<>(); - timescaleTsKvEntities.forEach(entity -> { - if (entity != null && entity.isNotEmpty()) { - entity.setEntityId(entityId.getId()); - entity.setStrKey(key); - result.add(Optional.of(DaoUtil.getData(entity))); - } else { - result.add(Optional.empty()); - } - }); - return result; - } else { - return Collections.emptyList(); - } - }, MoreExecutors.directExecutor()); + private List> findAllAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long timeBucket, Aggregation aggregation) { + long interval = endTs - startTs; + long remainingPart = interval % timeBucket; + List timescaleTsKvEntities; + if (remainingPart == 0) { + timescaleTsKvEntities = switchAggregation(key, startTs, endTs, timeBucket, aggregation, entityId.getId()); + } else { + interval = interval - remainingPart; + timescaleTsKvEntities = new ArrayList<>(); + timescaleTsKvEntities.addAll(switchAggregation(key, startTs, startTs + interval, timeBucket, aggregation, entityId.getId())); + timescaleTsKvEntities.addAll(switchAggregation(key, startTs + interval, endTs, remainingPart, aggregation, entityId.getId())); + } + + if (!CollectionUtils.isEmpty(timescaleTsKvEntities)) { + List> result = new ArrayList<>(); + timescaleTsKvEntities.forEach(entity -> { + if (entity != null && entity.isNotEmpty()) { + entity.setEntityId(entityId.getId()); + entity.setStrKey(key); + result.add(Optional.of(entity)); + } else { + result.add(Optional.empty()); + } + }); + return result; + } else { + return Collections.emptyList(); + } } - private CompletableFuture> switchAggregation(String key, long startTs, long endTs, long timeBucket, Aggregation aggregation, UUID entityId) { + private List switchAggregation(String key, long startTs, long endTs, long timeBucket, Aggregation aggregation, UUID entityId) { + Integer keyId = getOrSaveKeyId(key); switch (aggregation) { case AVG: - return findAvg(key, startTs, endTs, timeBucket, entityId); + return aggregationRepository.findAvg(entityId, keyId, timeBucket, startTs, endTs); case MAX: - return findMax(key, startTs, endTs, timeBucket, entityId); + return aggregationRepository.findMax(entityId, keyId, timeBucket, startTs, endTs); case MIN: - return findMin(key, startTs, endTs, timeBucket, entityId); + return aggregationRepository.findMin(entityId, keyId, timeBucket, startTs, endTs); case SUM: - return findSum(key, startTs, endTs, timeBucket, entityId); + return aggregationRepository.findSum(entityId, keyId, timeBucket, startTs, endTs); case COUNT: - return findCount(key, startTs, endTs, timeBucket, entityId); + return aggregationRepository.findCount(entityId, keyId, timeBucket, startTs, endTs); default: throw new IllegalArgumentException("Not supported aggregation type: " + aggregation); } } - private CompletableFuture> findCount(String key, long startTs, long endTs, long timeBucket, UUID entityId) { - Integer keyId = getOrSaveKeyId(key); - return aggregationRepository.findCount( - entityId, - keyId, - timeBucket, - startTs, - endTs); - } - - private CompletableFuture> findSum(String key, long startTs, long endTs, long timeBucket, UUID entityId) { - Integer keyId = getOrSaveKeyId(key); - return aggregationRepository.findSum( - entityId, - keyId, - timeBucket, - startTs, - endTs); - } - - private CompletableFuture> findMin(String key, long startTs, long endTs, long timeBucket, UUID entityId) { - Integer keyId = getOrSaveKeyId(key); - return aggregationRepository.findMin( - entityId, - keyId, - timeBucket, - startTs, - endTs); - } - - private CompletableFuture> findMax(String key, long startTs, long endTs, long timeBucket, UUID entityId) { - Integer keyId = getOrSaveKeyId(key); - return aggregationRepository.findMax( - entityId, - keyId, - timeBucket, - startTs, - endTs); - } - - private CompletableFuture> findAvg(String key, long startTs, long endTs, long timeBucket, UUID entityId) { - Integer keyId = getOrSaveKeyId(key); - return aggregationRepository.findAvg( - entityId, - keyId, - timeBucket, - startTs, - endTs); - } - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java index c603d9d92b..1d9817a5bd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java @@ -20,14 +20,12 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import org.springframework.scheduling.annotation.Async; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sqlts.ts.TsKvCompositeKey; import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; import java.util.List; import java.util.UUID; -import java.util.concurrent.CompletableFuture; public interface TsKvRepository extends JpaRepository { @@ -48,82 +46,75 @@ public interface TsKvRepository extends JpaRepository= :startTs AND tskv.ts < :endTs") - CompletableFuture findStringMax(@Param("entityId") UUID entityId, + TsKvEntity findStringMax(@Param("entityId") UUID entityId, @Param("entityKey") int entityKey, @Param("startTs") long startTs, @Param("endTs") long endTs); - @Async @Query("SELECT new TsKvEntity(MAX(COALESCE(tskv.longValue, -9223372036854775807)), " + "MAX(COALESCE(tskv.doubleValue, -1.79769E+308)), " + "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + - "'MAX') FROM TsKvEntity tskv " + + "'MAX', MAX(tskv.ts)) FROM TsKvEntity tskv " + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") - CompletableFuture findNumericMax(@Param("entityId") UUID entityId, + TsKvEntity findNumericMax(@Param("entityId") UUID entityId, @Param("entityKey") int entityKey, @Param("startTs") long startTs, @Param("endTs") long endTs); - @Async - @Query("SELECT new TsKvEntity(MIN(tskv.strValue)) FROM TsKvEntity tskv " + + @Query("SELECT new TsKvEntity(MIN(tskv.strValue), MAX(tskv.ts)) FROM TsKvEntity tskv " + "WHERE tskv.strValue IS NOT NULL " + "AND tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") - CompletableFuture findStringMin(@Param("entityId") UUID entityId, + TsKvEntity findStringMin(@Param("entityId") UUID entityId, @Param("entityKey") int entityKey, @Param("startTs") long startTs, @Param("endTs") long endTs); - @Async @Query("SELECT new TsKvEntity(MIN(COALESCE(tskv.longValue, 9223372036854775807)), " + "MIN(COALESCE(tskv.doubleValue, 1.79769E+308)), " + "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + - "'MIN') FROM TsKvEntity tskv " + + "'MIN', MAX(tskv.ts)) FROM TsKvEntity tskv " + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") - CompletableFuture findNumericMin( + TsKvEntity findNumericMin( @Param("entityId") UUID entityId, @Param("entityKey") int entityKey, @Param("startTs") long startTs, @Param("endTs") long endTs); - @Async @Query("SELECT new TsKvEntity(SUM(CASE WHEN tskv.booleanValue IS NULL THEN 0 ELSE 1 END), " + "SUM(CASE WHEN tskv.strValue IS NULL THEN 0 ELSE 1 END), " + "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + - "SUM(CASE WHEN tskv.jsonValue IS NULL THEN 0 ELSE 1 END)) FROM TsKvEntity tskv " + + "SUM(CASE WHEN tskv.jsonValue IS NULL THEN 0 ELSE 1 END), MAX(tskv.ts)) FROM TsKvEntity tskv " + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") - CompletableFuture findCount(@Param("entityId") UUID entityId, + TsKvEntity findCount(@Param("entityId") UUID entityId, @Param("entityKey") int entityKey, @Param("startTs") long startTs, @Param("endTs") long endTs); - @Async @Query("SELECT new TsKvEntity(SUM(COALESCE(tskv.longValue, 0)), " + "SUM(COALESCE(tskv.doubleValue, 0.0)), " + "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + - "'AVG') FROM TsKvEntity tskv " + + "'AVG', MAX(tskv.ts)) FROM TsKvEntity tskv " + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") - CompletableFuture findAvg(@Param("entityId") UUID entityId, + TsKvEntity findAvg(@Param("entityId") UUID entityId, @Param("entityKey") int entityKey, @Param("startTs") long startTs, @Param("endTs") long endTs); - @Async @Query("SELECT new TsKvEntity(SUM(COALESCE(tskv.longValue, 0)), " + "SUM(COALESCE(tskv.doubleValue, 0.0)), " + "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + - "'SUM') FROM TsKvEntity tskv " + + "'SUM', MAX(tskv.ts)) FROM TsKvEntity tskv " + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") - CompletableFuture findSum(@Param("entityId") UUID entityId, + TsKvEntity findSum(@Param("entityId") UUID entityId, @Param("entityKey") int entityKey, @Param("startTs") long startTs, @Param("endTs") long endTs); diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java index 8a2d3354d4..a1ffde92d5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java @@ -19,8 +19,6 @@ import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.TenantProfile; diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index 866150e679..25489f3e1a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -170,6 +170,11 @@ public class TenantServiceImpl extends AbstractCachedEntityService, Optional> { +public class AggregatePartitionsFunction implements com.google.common.util.concurrent.AsyncFunction, Optional> { private static final int LONG_CNT_POS = 0; private static final int DOUBLE_CNT_POS = 1; private static final int BOOL_CNT_POS = 2; private static final int STR_CNT_POS = 3; private static final int JSON_CNT_POS = 4; - private static final int LONG_POS = 5; - private static final int DOUBLE_POS = 6; - private static final int BOOL_POS = 7; - private static final int STR_POS = 8; - private static final int JSON_POS = 9; + private static final int MAX_TS_POS = 5; + private static final int LONG_POS = 6; + private static final int DOUBLE_POS = 7; + private static final int BOOL_POS = 8; + private static final int STR_POS = 9; + private static final int JSON_POS = 10; + private final Aggregation aggregation; private final String key; @@ -66,29 +70,29 @@ public class AggregatePartitionsFunction implements com.google.common.util.concu } @Override - public ListenableFuture> apply(@Nullable List rsList) { - log.trace("[{}][{}][{}] Going to aggregate data", key, ts, aggregation); - if (rsList == null || rsList.isEmpty()) { - return Futures.immediateFuture(Optional.empty()); - } - return Futures.transform( - Futures.allAsList( - rsList.stream().map(rs -> rs.allRows(this.executor)) - .collect(Collectors.toList())), - rowsList -> { - try { - AggregationResult aggResult = new AggregationResult(); - for (List rs : rowsList) { - for (Row row : rs) { - processResultSetRow(row, aggResult); + public ListenableFuture> apply(@Nullable List rsList) { + log.trace("[{}][{}][{}] Going to aggregate data", key, ts, aggregation); + if (rsList == null || rsList.isEmpty()) { + return Futures.immediateFuture(Optional.empty()); + } + return Futures.transform( + Futures.allAsList( + rsList.stream().map(rs -> rs.allRows(this.executor)) + .collect(Collectors.toList())), + rowsList -> { + try { + AggregationResult aggResult = new AggregationResult(); + for (List rs : rowsList) { + for (Row row : rs) { + processResultSetRow(row, aggResult); + } + } + return processAggregationResult(aggResult); + } catch (Exception e) { + log.error("[{}][{}][{}] Failed to aggregate data", key, ts, aggregation, e); + return Optional.empty(); } - } - return processAggregationResult(aggResult); - } catch (Exception e) { - log.error("[{}][{}][{}] Failed to aggregate data", key, ts, aggregation, e); - return Optional.empty(); - } - }, this.executor); + }, this.executor); } private void processResultSetRow(Row row, AggregationResult aggResult) { @@ -105,6 +109,7 @@ public class AggregatePartitionsFunction implements com.google.common.util.concu long boolCount = row.getLong(BOOL_CNT_POS); long strCount = row.getLong(STR_CNT_POS); long jsonCount = row.getLong(JSON_CNT_POS); + long aggValuesLastTs = row.getLong(MAX_TS_POS); if (longCount > 0 || doubleCount > 0) { if (longCount > 0) { @@ -134,6 +139,8 @@ public class AggregatePartitionsFunction implements com.google.common.util.concu return; } + aggResult.aggValuesLastTs = Math.max(aggResult.aggValuesLastTs, aggValuesLastTs); + if (aggregation == Aggregation.COUNT) { aggResult.count += curCount; } else if (aggregation == Aggregation.AVG || aggregation == Aggregation.SUM) { @@ -231,34 +238,37 @@ public class AggregatePartitionsFunction implements com.google.common.util.concu } } - private Optional processAggregationResult(AggregationResult aggResult) { + private Optional processAggregationResult(AggregationResult aggResult) { Optional result; if (aggResult.dataType == null) { result = Optional.empty(); } else if (aggregation == Aggregation.COUNT) { result = Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggResult.count))); } else if (aggregation == Aggregation.AVG || aggregation == Aggregation.SUM) { - result = processAvgOrSumResult(aggResult); + result = processAvgOrSumResult(aggregation, aggResult); } else if (aggregation == Aggregation.MIN || aggregation == Aggregation.MAX) { result = processMinOrMaxResult(aggResult); } else { result = Optional.empty(); } - if (!result.isPresent()) { + if (result.isEmpty()) { log.trace("[{}][{}][{}] Aggregated data is empty.", key, ts, aggregation); } - return result; + return result.map(tsKvEntry -> new TsKvEntryAggWrapper(tsKvEntry, aggResult.aggValuesLastTs)); } - private Optional processAvgOrSumResult(AggregationResult aggResult) { + private Optional processAvgOrSumResult(Aggregation aggregation, AggregationResult aggResult) { if (aggResult.count == 0 || (aggResult.dataType == DataType.DOUBLE && aggResult.dValue == null) || (aggResult.dataType == DataType.LONG && aggResult.lValue == null)) { return Optional.empty(); } else if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) { if (aggregation == Aggregation.AVG || aggResult.hasDouble) { double sum = Optional.ofNullable(aggResult.dValue).orElse(0.0d) + Optional.ofNullable(aggResult.lValue).orElse(0L); - return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.SUM ? sum : (sum / aggResult.count)))); + DoubleDataEntry doubleDataEntry = new DoubleDataEntry(key, aggregation == Aggregation.SUM ? sum : (sum / aggResult.count)); + TsKvEntry result = aggregation == Aggregation.AVG ? new AggTsKvEntry(ts, doubleDataEntry, aggResult.count) : new BasicTsKvEntry(ts, doubleDataEntry); + return Optional.of(result); } else { - return Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggregation == Aggregation.SUM ? aggResult.lValue : (aggResult.lValue / aggResult.count)))); + LongDataEntry longDataEntry = new LongDataEntry(key, aggregation == Aggregation.SUM ? aggResult.lValue : (aggResult.lValue / aggResult.count)); + return Optional.of(new BasicTsKvEntry(ts, longDataEntry)); } } return Optional.empty(); @@ -291,5 +301,6 @@ public class AggregatePartitionsFunction implements com.google.common.util.concu Long lValue = null; long count = 0; boolean hasDouble = false; + long aggValuesLastTs = 0; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java index a74f51ce9e..b4cb27cb53 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java @@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult; import org.thingsboard.server.dao.entityview.EntityViewService; @@ -45,13 +46,15 @@ import org.thingsboard.server.dao.service.Validator; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; -import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.thingsboard.server.common.data.StringUtils.isBlank; /** * @author Andrew Shvayka */ +@SuppressWarnings("UnstableApiUsage") @Service @Slf4j public class BaseTimeseriesService implements TimeseriesService { @@ -59,7 +62,7 @@ public class BaseTimeseriesService implements TimeseriesService { private static final int INSERTS_PER_ENTRY = 3; private static final int INSERTS_PER_ENTRY_WITHOUT_LATEST = 2; private static final int DELETES_PER_ENTRY = INSERTS_PER_ENTRY; - public static final Function, Integer> SUM_ALL_INTEGERS = new Function, Integer>() { + public static final Function, Integer> SUM_ALL_INTEGERS = new Function<>() { @Override public @Nullable Integer apply(@Nullable List input) { int result = 0; @@ -87,7 +90,7 @@ public class BaseTimeseriesService implements TimeseriesService { private EntityViewService entityViewService; @Override - public ListenableFuture> findAll(TenantId tenantId, EntityId entityId, List queries) { + public ListenableFuture> findAllByQueries(TenantId tenantId, EntityId entityId, List queries) { validate(entityId); queries.forEach(this::validate); if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) { @@ -103,6 +106,17 @@ public class BaseTimeseriesService implements TimeseriesService { return timeseriesDao.findAllAsync(tenantId, entityId, queries); } + @Override + public ListenableFuture> findAll(TenantId tenantId, EntityId entityId, List queries) { + return Futures.transform(findAllByQueries(tenantId, entityId, queries), + result -> { + if (result != null && !result.isEmpty()) { + return result.stream().map(ReadTsKvQueryResult::getData).flatMap(Collection::stream).collect(Collectors.toList()); + } + return Collections.emptyList(); + }, MoreExecutors.directExecutor()); + } + @Override public ListenableFuture> findLatest(TenantId tenantId, EntityId entityId, Collection keys) { validate(entityId); @@ -244,7 +258,7 @@ public class BaseTimeseriesService implements TimeseriesService { public ListenableFuture> removeAllLatest(TenantId tenantId, EntityId entityId) { validate(entityId); return Futures.transformAsync(this.findAllLatest(tenantId, entityId), latest -> { - if (!latest.isEmpty()) { + if (latest != null && !latest.isEmpty()) { Collection keys = latest.stream().map(TsKvEntry::getKey).collect(Collectors.toList()); return Futures.transform(this.removeLatest(tenantId, entityId, keys), res -> keys, MoreExecutors.directExecutor()); } else { diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index 3b5e45f206..367ad942a7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -42,7 +42,9 @@ import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.kv.TsKvEntryAggWrapper; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.nosql.TbResultSet; import org.thingsboard.server.dao.nosql.TbResultSetFuture; @@ -71,6 +73,7 @@ import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; /** * @author Andrew Shvayka */ +@SuppressWarnings("UnstableApiUsage") @Component @Slf4j @NoSqlTsDao @@ -139,20 +142,10 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD } @Override - public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { - List>> futures = queries.stream().map(query -> findAllAsync(tenantId, entityId, query)).collect(Collectors.toList()); - return Futures.transform(Futures.allAsList(futures), new Function<>() { - @Nullable - @Override - public List apply(@Nullable List> results) { - if (results == null || results.isEmpty()) { - return null; - } - return results.stream() - .flatMap(List::stream) - .collect(Collectors.toList()); - } - }, readResultsProcessingExecutor); + public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { + List> futures = queries.stream() + .map(query -> findAllAsync(tenantId, entityId, query)).collect(Collectors.toList()); + return Futures.allAsList(futures); } @Override @@ -270,28 +263,42 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD } @Override - public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { + public ListenableFuture findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { if (query.getAggregation() == Aggregation.NONE) { return findAllAsyncWithLimit(tenantId, entityId, query); } else { long startPeriod = query.getStartTs(); - long endPeriod = query.getEndTs(); + long endPeriod = Math.max(query.getStartTs() + 1, query.getEndTs()); long step = Math.max(query.getInterval(), MIN_AGGREGATION_STEP_MS); - List>> futures = new ArrayList<>(); - while (startPeriod <= endPeriod) { + List>> futures = new ArrayList<>(); + while (startPeriod < endPeriod) { long startTs = startPeriod; - long endTs = Math.min(startPeriod + step, endPeriod + 1); + long endTs = Math.min(startPeriod + step, endPeriod); long ts = endTs - startTs; ReadTsKvQuery subQuery = new BaseReadTsKvQuery(query.getKey(), startTs, endTs, ts, 1, query.getAggregation(), query.getOrder()); futures.add(findAndAggregateAsync(tenantId, entityId, subQuery, toPartitionTs(startTs), toPartitionTs(endTs))); startPeriod = endTs; } - ListenableFuture>> future = Futures.allAsList(futures); + ListenableFuture>> future = Futures.allAsList(futures); return Futures.transform(future, new Function<>() { @Nullable @Override - public List apply(@Nullable List> input) { - return input == null ? Collections.emptyList() : input.stream().filter(v -> v.isPresent()).map(v -> v.get()).collect(Collectors.toList()); + public ReadTsKvQueryResult apply(@Nullable List> input) { + if (input == null) { + return new ReadTsKvQueryResult(query.getId(), Collections.emptyList(), query.getStartTs()); + } else { + long maxTs = query.getStartTs(); + List data = new ArrayList<>(); + for (var opt : input) { + if (opt.isPresent()) { + TsKvEntryAggWrapper tsKvEntryAggWrapper = opt.get(); + maxTs = Math.max(maxTs, tsKvEntryAggWrapper.getLastEntryTs()); + data.add(tsKvEntryAggWrapper.getEntry()); + } + } + return new ReadTsKvQueryResult(query.getId(), data, maxTs); + } + } }, readResultsProcessingExecutor); } @@ -302,13 +309,13 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD //Cleanup by TTL is native for Cassandra } - private ListenableFuture> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { + private ListenableFuture findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { long minPartition = toPartitionTs(query.getStartTs()); long maxPartition = toPartitionTs(query.getEndTs()); final ListenableFuture> partitionsListFuture = getPartitionsFuture(tenantId, query, entityId, minPartition, maxPartition); final SimpleListenableFuture> resultFuture = new SimpleListenableFuture<>(); - Futures.addCallback(partitionsListFuture, new FutureCallback>() { + Futures.addCallback(partitionsListFuture, new FutureCallback<>() { @Override public void onSuccess(@Nullable List partitions) { TsKvQueryCursor cursor = new TsKvQueryCursor(entityId.getEntityType().name(), entityId.getId(), query, partitions); @@ -321,7 +328,13 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD } }, readResultsProcessingExecutor); - return resultFuture; + return Futures.transform(resultFuture, tsKvEntries -> { + long lastTs = query.getStartTs(); + if (tsKvEntries != null) { + lastTs = tsKvEntries.stream().map(TsKvEntry::getTs).max(Long::compare).orElse(query.getStartTs()); + } + return new ReadTsKvQueryResult(query.getId(), tsKvEntries, lastTs); + }, MoreExecutors.directExecutor()); } private long toPartitionTs(long ts) { @@ -379,7 +392,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD } } - private ListenableFuture> findAndAggregateAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query, long minPartition, long maxPartition) { + private ListenableFuture> findAndAggregateAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query, long minPartition, long maxPartition) { final Aggregation aggregation = query.getAggregation(); final String key = query.getKey(); final long startTs = query.getStartTs(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesLatestDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesLatestDao.java index 124af03a1a..2b3d62712f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesLatestDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesLatestDao.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult; import org.thingsboard.server.dao.model.ModelConstants; @@ -145,9 +146,10 @@ public class CassandraBaseTimeseriesLatestDao extends AbstractCassandraBaseTimes long endTs = query.getStartTs() - 1; ReadTsKvQuery findNewLatestQuery = new BaseReadTsKvQuery(query.getKey(), startTs, endTs, endTs - startTs, 1, Aggregation.NONE, DESC_ORDER); - ListenableFuture> future = aggregationTimeseriesDao.findAllAsync(tenantId, entityId, findNewLatestQuery); + ListenableFuture future = aggregationTimeseriesDao.findAllAsync(tenantId, entityId, findNewLatestQuery); - return Futures.transformAsync(future, entryList -> { + return Futures.transformAsync(future, result -> { + var entryList = result.getData(); if (entryList.size() == 1) { TsKvEntry entry = entryList.get(0); return Futures.transform(saveLatest(tenantId, entityId, entryList.get(0)), v -> new TsKvLatestRemovingResult(entry), MoreExecutors.directExecutor()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java index c075a51434..5fd26d400a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java @@ -20,16 +20,18 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.common.data.kv.TsKvEntry; import java.util.List; +import java.util.Map; /** * @author Andrew Shvayka */ public interface TimeseriesDao { - ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries); + ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries); ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl); diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index 7c8a8ca27b..fc0de8a4fa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -21,13 +21,14 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.security.authentication.DisabledException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; @@ -111,7 +112,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic if (user.getId() == null) { UserCredentials userCredentials = new UserCredentials(); userCredentials.setEnabled(false); - userCredentials.setActivateToken(RandomStringUtils.randomAlphanumeric(DEFAULT_TOKEN_LENGTH)); + userCredentials.setActivateToken(StringUtils.randomAlphanumeric(DEFAULT_TOKEN_LENGTH)); userCredentials.setUserId(new UserId(savedUser.getUuidId())); saveUserCredentialsAndPasswordHistory(user.getTenantId(), userCredentials); } @@ -177,7 +178,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic if (!userCredentials.isEnabled()) { throw new DisabledException(String.format("User credentials not enabled [%s]", email)); } - userCredentials.setResetToken(RandomStringUtils.randomAlphanumeric(DEFAULT_TOKEN_LENGTH)); + userCredentials.setResetToken(StringUtils.randomAlphanumeric(DEFAULT_TOKEN_LENGTH)); return saveUserCredentials(tenantId, userCredentials); } @@ -187,7 +188,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic if (!userCredentials.isEnabled()) { throw new IncorrectParameterException("Unable to reset password for inactive user"); } - userCredentials.setResetToken(RandomStringUtils.randomAlphanumeric(DEFAULT_TOKEN_LENGTH)); + userCredentials.setResetToken(StringUtils.randomAlphanumeric(DEFAULT_TOKEN_LENGTH)); return saveUserCredentials(tenantId, userCredentials); } @@ -201,6 +202,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic } @Override + @Transactional public void deleteUser(TenantId tenantId, UserId userId) { log.trace("Executing deleteUser [{}]", userId); validateId(userId, INCORRECT_USER_ID + userId); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 22f36a2bfb..e93221508e 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -722,38 +722,6 @@ CREATE TABLE IF NOT EXISTS rpc ( status varchar(255) NOT NULL ); -CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl( - IN regular_events_start_ts bigint, - IN regular_events_end_ts bigint, - IN debug_events_start_ts bigint, - IN debug_events_end_ts bigint, - INOUT deleted bigint) - LANGUAGE plpgsql AS -$$ -DECLARE - ttl_deleted_count bigint DEFAULT 0; - debug_ttl_deleted_count bigint DEFAULT 0; -BEGIN - IF regular_events_start_ts > 0 AND regular_events_end_ts > 0 THEN - EXECUTE format( - 'WITH deleted AS (DELETE FROM event WHERE id in (SELECT id from event WHERE ts > %L::bigint AND ts < %L::bigint AND ' || - '(event_type != %L::varchar AND event_type != %L::varchar)) RETURNING *) ' || - 'SELECT count(*) FROM deleted', regular_events_start_ts, regular_events_end_ts, - 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into ttl_deleted_count; - END IF; - IF debug_events_start_ts > 0 AND debug_events_end_ts > 0 THEN - EXECUTE format( - 'WITH deleted AS (DELETE FROM event WHERE id in (SELECT id from event WHERE ts > %L::bigint AND ts < %L::bigint AND ' || - '(event_type = %L::varchar OR event_type = %L::varchar)) RETURNING *) ' || - 'SELECT count(*) FROM deleted', debug_events_start_ts, debug_events_end_ts, - 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into debug_ttl_deleted_count; - END IF; - RAISE NOTICE 'Events removed by ttl: %', ttl_deleted_count; - RAISE NOTICE 'Debug Events removed by ttl: %', debug_ttl_deleted_count; - deleted := ttl_deleted_count + debug_ttl_deleted_count; -END -$$; - CREATE OR REPLACE FUNCTION to_uuid(IN entity_id varchar, OUT uuid_id uuid) AS $$ BEGIN diff --git a/dao/src/test/java/org/thingsboard/server/dao/TimescaleDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/TimescaleDaoServiceTestSuite.java new file mode 100644 index 0000000000..6c29aea9ee --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/TimescaleDaoServiceTestSuite.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao; + +import org.junit.extensions.cpsuite.ClasspathSuite; +import org.junit.extensions.cpsuite.ClasspathSuite.ClassnameFilters; +import org.junit.runner.RunWith; + +@RunWith(ClasspathSuite.class) +@ClassnameFilters({ + "org.thingsboard.server.dao.service.*.nosql.*ServiceTimescaleTest", +}) +public class TimescaleDaoServiceTestSuite { + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/TimescaleSqlInitializer.java b/dao/src/test/java/org/thingsboard/server/dao/TimescaleSqlInitializer.java new file mode 100644 index 0000000000..991f959937 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/TimescaleSqlInitializer.java @@ -0,0 +1,65 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao; + +import com.google.common.base.Charsets; +import com.google.common.io.Resources; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.net.URL; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; + +@Slf4j +public class TimescaleSqlInitializer { + + private static final List sqlFiles = List.of( + "sql/schema-timescale.sql", + "sql/schema-entities.sql", + "sql/schema-entities-idx.sql", + "sql/schema-entities-idx-psql-addon.sql", + "sql/system-data.sql", + "sql/system-test-psql.sql"); + private static final String dropAllTablesSqlFile = "sql/timescale/drop-all-tables.sql"; + + public static void initDb(Connection conn) { + cleanUpDb(conn); + log.info("initialize Timescale DB..."); + try { + for (String sqlFile : sqlFiles) { + URL sqlFileUrl = Resources.getResource(sqlFile); + String sql = Resources.toString(sqlFileUrl, Charsets.UTF_8); + conn.createStatement().execute(sql); + } + } catch (IOException | SQLException e) { + throw new RuntimeException("Unable to init the Timescale database. Reason: " + e.getMessage(), e); + } + log.info("Timescale DB is initialized!"); + } + + private static void cleanUpDb(Connection conn) { + log.info("clean up Timescale DB..."); + try { + URL dropAllTableSqlFileUrl = Resources.getResource(dropAllTablesSqlFile); + String dropAllTablesSql = Resources.toString(dropAllTableSqlFileUrl, Charsets.UTF_8); + conn.createStatement().execute(dropAllTablesSql); + } catch (IOException | SQLException e) { + throw new RuntimeException("Unable to clean up the Timescale database. Reason: " + e.getMessage(), e); + } + } +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java index 475f84a29a..674d00d5f6 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java @@ -18,7 +18,6 @@ package org.thingsboard.server.dao.service; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -33,14 +32,13 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.EventInfo; import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.event.Event; import org.thingsboard.server.common.data.event.RuleNodeDebugEvent; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; @@ -265,8 +263,8 @@ public abstract class AbstractServiceTest { edge.setTenantId(tenantId); edge.setName(name); edge.setType(type); - edge.setSecret(RandomStringUtils.randomAlphanumeric(20)); - edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20)); + edge.setSecret(StringUtils.randomAlphanumeric(20)); + edge.setRoutingKey(StringUtils.randomAlphanumeric(20)); return edge; } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java index a4bffa9d65..0a6cd0f33c 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java @@ -16,13 +16,13 @@ package org.thingsboard.server.dao.service; import com.datastax.oss.driver.api.core.uuid.Uuids; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntitySubtype; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetInfo; @@ -257,7 +257,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { for (int i=0;i<143;i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1+suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); @@ -269,7 +269,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { for (int i=0;i<175;i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2+suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); @@ -335,7 +335,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { for (int i=0;i<143;i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1+suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); @@ -348,7 +348,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { for (int i=0;i<175;i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2+suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); @@ -470,7 +470,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { for (int i=0;i<175;i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1+suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); @@ -483,7 +483,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { for (int i=0;i<143;i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2+suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); @@ -558,7 +558,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { for (int i=0;i<175;i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1+suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); @@ -572,7 +572,7 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { for (int i=0;i<143;i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2+suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); asset.setName(name); @@ -634,8 +634,8 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest { @Test public void testCleanCacheIfAssetRenamed() { - String assetNameBeforeRename = RandomStringUtils.randomAlphanumeric(15); - String assetNameAfterRename = RandomStringUtils.randomAlphanumeric(15); + String assetNameBeforeRename = StringUtils.randomAlphanumeric(15); + String assetNameAfterRename = StringUtils.randomAlphanumeric(15); Asset asset = new Asset(); asset.setTenantId(tenantId); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseCustomerServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseCustomerServiceTest.java index 4dd3169579..f494b330e2 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseCustomerServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseCustomerServiceTest.java @@ -20,13 +20,13 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -189,7 +189,7 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest { for (int i = 0; i < 143; i++) { Customer customer = new Customer(); customer.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); + String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); String title = title1 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); customer.setTitle(title); @@ -202,7 +202,7 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest { for (int i = 0; i < 175; i++) { Customer customer = new Customer(); customer.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); + String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); String title = title2 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); customer.setTitle(title); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java index 770b215adf..cd40ea8761 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.dao.service; import com.datastax.oss.driver.api.core.uuid.Uuids; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -24,6 +23,7 @@ import org.junit.Test; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DashboardInfo; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; @@ -272,7 +272,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { for (int i=0;i<123;i++) { Dashboard dashboard = new Dashboard(); dashboard.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*17)); + String suffix = StringUtils.randomAlphanumeric((int)(Math.random()*17)); String title = title1+suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); dashboard.setTitle(title); @@ -283,7 +283,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { for (int i=0;i<193;i++) { Dashboard dashboard = new Dashboard(); dashboard.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); + String suffix = StringUtils.randomAlphanumeric((int)(Math.random()*15)); String title = title2+suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); dashboard.setTitle(title); @@ -416,8 +416,8 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { edge.setType("default"); edge.setName("Test different edge"); edge.setType("default"); - edge.setSecret(RandomStringUtils.randomAlphanumeric(20)); - edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20)); + edge.setSecret(StringUtils.randomAlphanumeric(20)); + edge.setRoutingKey(StringUtils.randomAlphanumeric(20)); edge = edgeService.saveEdge(edge); try { dashboardService.assignDashboardToEdge(tenantId, dashboard.getId(), edge.getId()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsCacheTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsCacheTest.java index 3bbe6f22ab..111ec63188 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsCacheTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsCacheTest.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao.service; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -26,13 +25,13 @@ import org.springframework.cache.CacheManager; import org.springframework.test.util.ReflectionTestUtils; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceCredentialsId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.dao.device.DeviceCredentialsDao; import org.thingsboard.server.dao.device.DeviceCredentialsService; -import org.thingsboard.server.dao.device.DeviceDao; import org.thingsboard.server.dao.device.DeviceService; import java.util.UUID; @@ -44,8 +43,8 @@ import static org.mockito.Mockito.when; public abstract class BaseDeviceCredentialsCacheTest extends AbstractServiceTest { - private static final String CREDENTIALS_ID_1 = RandomStringUtils.randomAlphanumeric(20); - private static final String CREDENTIALS_ID_2 = RandomStringUtils.randomAlphanumeric(20); + private static final String CREDENTIALS_ID_1 = StringUtils.randomAlphanumeric(20); + private static final String CREDENTIALS_ID_2 = StringUtils.randomAlphanumeric(20); @Autowired private DeviceCredentialsService deviceCredentialsService; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java index b4c0ff77dd..40d508f84b 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.dao.service; import com.datastax.oss.driver.api.core.uuid.Uuids; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -29,11 +28,12 @@ import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.DeviceCredentials; @@ -424,7 +424,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { for (int i = 0; i < 143; i++) { Device device = new Device(); device.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); @@ -436,7 +436,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { for (int i = 0; i < 175; i++) { Device device = new Device(); device.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); @@ -502,7 +502,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { for (int i = 0; i < 143; i++) { Device device = new Device(); device.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); @@ -515,7 +515,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { for (int i = 0; i < 175; i++) { Device device = new Device(); device.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); @@ -637,7 +637,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { for (int i = 0; i < 175; i++) { Device device = new Device(); device.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); @@ -650,7 +650,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { for (int i = 0; i < 143; i++) { Device device = new Device(); device.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); @@ -725,7 +725,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { for (int i = 0; i < 175; i++) { Device device = new Device(); device.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); @@ -739,7 +739,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { for (int i = 0; i < 143; i++) { Device device = new Device(); device.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); @@ -801,8 +801,8 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { @Test public void testCleanCacheIfDeviceRenamed() { - String deviceNameBeforeRename = RandomStringUtils.randomAlphanumeric(15); - String deviceNameAfterRename = RandomStringUtils.randomAlphanumeric(15); + String deviceNameBeforeRename = StringUtils.randomAlphanumeric(15); + String deviceNameAfterRename = StringUtils.randomAlphanumeric(15); Device device = new Device(); device.setTenantId(tenantId); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeServiceTest.java index 093bd386a7..6b3ceba23f 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeServiceTest.java @@ -17,7 +17,6 @@ package org.thingsboard.server.dao.service; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.databind.node.ObjectNode; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -25,6 +24,7 @@ import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntitySubtype; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; @@ -236,7 +236,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { String title1 = "Edge title 1"; List edgesTitle1 = new ArrayList<>(); for (int i = 0; i < 143; i++) { - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, "default"); @@ -245,7 +245,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { String title2 = "Edge title 2"; List edgesTitle2 = new ArrayList<>(); for (int i = 0; i < 175; i++) { - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, "default"); @@ -308,7 +308,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { String type1 = "typeA"; List edgesType1 = new ArrayList<>(); for (int i = 0; i < 143; i++) { - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, type1); @@ -318,7 +318,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { String type2 = "typeB"; List edgesType2 = new ArrayList<>(); for (int i = 0; i < 175; i++) { - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, type2); @@ -434,7 +434,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { String title1 = "Edge title 1"; List edgesTitle1 = new ArrayList<>(); for (int i = 0; i < 175; i++) { - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, "default"); @@ -444,7 +444,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { String title2 = "Edge title 2"; List edgesTitle2 = new ArrayList<>(); for (int i = 0; i < 143; i++) { - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, "default"); @@ -516,7 +516,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { String type1 = "typeC"; List edgesType1 = new ArrayList<>(); for (int i = 0; i < 175; i++) { - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, type1); @@ -527,7 +527,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { String type2 = "typeD"; List edgesType2 = new ArrayList<>(); for (int i = 0; i < 143; i++) { - String suffix = RandomStringUtils.randomAlphanumeric(15); + String suffix = StringUtils.randomAlphanumeric(15); String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, type2); @@ -592,8 +592,8 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { @Test public void testCleanCacheIfEdgeRenamed() { - String edgeNameBeforeRename = RandomStringUtils.randomAlphanumeric(15); - String edgeNameAfterRename = RandomStringUtils.randomAlphanumeric(15); + String edgeNameBeforeRename = StringUtils.randomAlphanumeric(15); + String edgeNameAfterRename = StringUtils.randomAlphanumeric(15); Edge edge = constructEdge(tenantId, edgeNameBeforeRename, "default"); edgeService.saveEdge(edge); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java index 808e499239..5cfd4667de 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java @@ -19,9 +19,7 @@ import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomUtils; -import org.apache.commons.lang3.StringUtils; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Assert; @@ -33,6 +31,7 @@ import org.springframework.jdbc.core.ResultSetExtractor; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.edge.Edge; @@ -312,8 +311,8 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { edge.setName("Edge" + i); edge.setType(type); edge.setLabel("EdgeLabel" + i); - edge.setSecret(RandomStringUtils.randomAlphanumeric(20)); - edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20)); + edge.setSecret(StringUtils.randomAlphanumeric(20)); + edge.setRoutingKey(StringUtils.randomAlphanumeric(20)); return edge; } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index 592a541f81..5a7a5e33bb 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -16,12 +16,12 @@ package org.thingsboard.server.dao.service; import com.google.common.collect.Lists; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.oauth2.MapperType; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; @@ -654,7 +654,7 @@ public abstract class BaseOAuth2ServiceTest extends AbstractServiceTest { private OAuth2MobileInfo validMobileInfo(String pkgName, String appSecret) { return OAuth2MobileInfo.builder().pkgName(pkgName) - .appSecret(appSecret != null ? appSecret : RandomStringUtils.randomAlphanumeric(24)) + .appSecret(appSecret != null ? appSecret : StringUtils.randomAlphanumeric(24)) .build(); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java index ade4ce8c28..f4ddb45eed 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.dao.service; import com.datastax.oss.driver.api.core.uuid.Uuids; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -28,6 +27,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -38,7 +38,6 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.exception.DataValidationException; -import javax.validation.ValidationException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; @@ -669,7 +668,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { OtaPackageInfo firmwareInfo = new OtaPackageInfo(); firmwareInfo.setDeviceProfileId(deviceProfileId); firmwareInfo.setType(FIRMWARE); - firmwareInfo.setTitle(RandomStringUtils.random(257)); + firmwareInfo.setTitle(StringUtils.random(257)); firmwareInfo.setVersion(VERSION); firmwareInfo.setUrl(URL); firmwareInfo.setTenantId(tenantId); @@ -689,7 +688,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { firmwareInfo.setTenantId(tenantId); firmwareInfo.setTitle(TITLE); - firmwareInfo.setVersion(RandomStringUtils.random(257)); + firmwareInfo.setVersion(StringUtils.random(257)); thrown.expectMessage("length of version must be equal or less than 255"); otaPackageService.saveOtaPackageInfo(firmwareInfo, true); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java index 92a3a6af3a..92dabab6c6 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java @@ -17,11 +17,11 @@ package org.thingsboard.server.dao.service; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.RuleChainId; @@ -175,7 +175,7 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest { for (int i = 0; i < 123; i++) { RuleChain ruleChain = new RuleChain(); ruleChain.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric((int) (Math.random() * 17)); + String suffix = StringUtils.randomAlphanumeric((int) (Math.random() * 17)); String name = name1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); ruleChain.setName(name); @@ -186,7 +186,7 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest { for (int i = 0; i < 193; i++) { RuleChain ruleChain = new RuleChain(); ruleChain.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric((int) (Math.random() * 15)); + String suffix = StringUtils.randomAlphanumeric((int) (Math.random() * 15)); String name = name2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); ruleChain.setName(name); @@ -502,7 +502,7 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest { for (int i = 0; i < 123; i++) { RuleChain ruleChain = new RuleChain(); ruleChain.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric((int) (Math.random() * 17)); + String suffix = StringUtils.randomAlphanumeric((int) (Math.random() * 17)); String name = name1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); ruleChain.setName(name); @@ -516,7 +516,7 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest { for (int i = 0; i < 193; i++) { RuleChain ruleChain = new RuleChain(); ruleChain.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric((int) (Math.random() * 15)); + String suffix = StringUtils.randomAlphanumeric((int) (Math.random() * 15)); String name = name2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); ruleChain.setName(name); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantServiceTest.java index 4a48c56157..cad0fcf596 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantServiceTest.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao.service; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; @@ -34,6 +33,7 @@ import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.ResourceType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.Tenant; @@ -192,7 +192,7 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { List tenantsTitle1 = new ArrayList<>(); for (int i = 0; i < 134; i++) { Tenant tenant = new Tenant(); - String suffix = RandomStringUtils.randomAlphanumeric((int) (Math.random() * 15)); + String suffix = StringUtils.randomAlphanumeric((int) (Math.random() * 15)); String title = title1 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); tenant.setTitle(title); @@ -202,7 +202,7 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { List tenantsTitle2 = new ArrayList<>(); for (int i = 0; i < 127; i++) { Tenant tenant = new Tenant(); - String suffix = RandomStringUtils.randomAlphanumeric((int) (Math.random() * 15)); + String suffix = StringUtils.randomAlphanumeric((int) (Math.random() * 15)); String title = title2 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); tenant.setTitle(title); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java index 532a12dca2..3d8b3c0cf8 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseUserServiceTest.java @@ -15,12 +15,12 @@ */ package org.thingsboard.server.dao.service; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.CustomerId; @@ -248,7 +248,7 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { User user = new User(); user.setAuthority(Authority.TENANT_ADMIN); user.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); + String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); String email = email1 + suffix + "@thingsboard.org"; email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); user.setEmail(email); @@ -262,7 +262,7 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { User user = new User(); user.setAuthority(Authority.TENANT_ADMIN); user.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); + String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); String email = email2 + suffix + "@thingsboard.org"; email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); user.setEmail(email); @@ -400,7 +400,7 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { user.setAuthority(Authority.CUSTOMER_USER); user.setTenantId(tenantId); user.setCustomerId(customerId); - String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); + String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); String email = email1 + suffix + "@thingsboard.org"; email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); user.setEmail(email); @@ -415,7 +415,7 @@ public abstract class BaseUserServiceTest extends AbstractServiceTest { user.setAuthority(Authority.CUSTOMER_USER); user.setTenantId(tenantId); user.setCustomerId(customerId); - String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); + String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); String email = email2 + suffix + "@thingsboard.org"; email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); user.setEmail(email); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DaoTimescaleTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DaoTimescaleTest.java new file mode 100644 index 0000000000..237f539cf4 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/DaoTimescaleTest.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.service; + +import org.springframework.test.context.TestPropertySource; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@TestPropertySource(locations = {"classpath:application-test.properties", "classpath:timescale-test.properties"}) +public @interface DaoTimescaleTest { +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java index 5339863044..4e2fd17846 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java @@ -213,17 +213,17 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { } saveEntries(deviceId, TS + 100L + 1L); - List queries = List.of(new BaseReadTsKvQuery(LONG_KEY, TS, TS + 100, 101, 1, Aggregation.COUNT, DESC_ORDER)); + List queries = List.of(new BaseReadTsKvQuery(LONG_KEY, TS, TS + 100, 100, 1, Aggregation.COUNT, DESC_ORDER)); List entries = tsService.findAll(tenantId, deviceId, queries).get(); Assert.assertEquals(1, entries.size()); - Assert.assertEquals(toTsEntry(TS + 50, new LongDataEntry(LONG_KEY, 11L)), entries.get(0)); + Assert.assertEquals(toTsEntry(TS + 50, new LongDataEntry(LONG_KEY, 10L)), entries.get(0)); EntityView entityView = saveAndCreateEntityView(deviceId, List.of(LONG_KEY)); entries = tsService.findAll(tenantId, entityView.getId(), queries).get(); Assert.assertEquals(1, entries.size()); - Assert.assertEquals(toTsEntry(TS + 50, new LongDataEntry(LONG_KEY, 11L)), entries.get(0)); + Assert.assertEquals(toTsEntry(TS + 50, new LongDataEntry(LONG_KEY, 10L)), entries.get(0)); } @Test @@ -240,14 +240,14 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { List entries = tsService.findAll(tenantId, deviceId, queries).get(); Assert.assertEquals(2, entries.size()); Assert.assertEquals(toTsEntry(TS + 25000, new LongDataEntry(LONG_KEY, 5L)), entries.get(0)); - Assert.assertEquals(toTsEntry(TS + 75000, new LongDataEntry(LONG_KEY, 5L)), entries.get(1)); + Assert.assertEquals(toTsEntry(TS + 75000 - 1, new LongDataEntry(LONG_KEY, 5L)), entries.get(1)); EntityView entityView = saveAndCreateEntityView(deviceId, List.of(LONG_KEY)); entries = tsService.findAll(tenantId, entityView.getId(), queries).get(); Assert.assertEquals(2, entries.size()); Assert.assertEquals(toTsEntry(TS + 25000, new LongDataEntry(LONG_KEY, 5L)), entries.get(0)); - Assert.assertEquals(toTsEntry(TS + 75000, new LongDataEntry(LONG_KEY, 5L)), entries.get(1)); + Assert.assertEquals(toTsEntry(TS + 75000 - 1, new LongDataEntry(LONG_KEY, 5L)), entries.get(1)); } @Test @@ -264,14 +264,14 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { List entries = tsService.findAll(tenantId, deviceId, queries).get(); Assert.assertEquals(2, entries.size()); Assert.assertEquals(toTsEntry(TS + 25000, new LongDataEntry(LONG_KEY, 5L)), entries.get(0)); - Assert.assertEquals(toTsEntry(TS + 65000, new LongDataEntry(LONG_KEY, 4L)), entries.get(1)); + Assert.assertEquals(toTsEntry(TS + 65000, new LongDataEntry(LONG_KEY, 3L)), entries.get(1)); EntityView entityView = saveAndCreateEntityView(deviceId, List.of(LONG_KEY)); entries = tsService.findAll(tenantId, entityView.getId(), queries).get(); Assert.assertEquals(2, entries.size()); Assert.assertEquals(toTsEntry(TS + 25000, new LongDataEntry(LONG_KEY, 5L)), entries.get(0)); - Assert.assertEquals(toTsEntry(TS + 65000, new LongDataEntry(LONG_KEY, 4L)), entries.get(1)); + Assert.assertEquals(toTsEntry(TS + 65000, new LongDataEntry(LONG_KEY, 3L)), entries.get(1)); } @Test @@ -286,14 +286,14 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { List entries = tsService.findAll(tenantId, deviceId, queries).get(); Assert.assertEquals(2, entries.size()); Assert.assertEquals(toTsEntry(TS + 25000, new LongDataEntry(LONG_KEY, 5L)), entries.get(0)); - Assert.assertEquals(toTsEntry(TS + 75000, new LongDataEntry(LONG_KEY, 5L)), entries.get(1)); + Assert.assertEquals(toTsEntry(TS + 75000 - 1, new LongDataEntry(LONG_KEY, 4L)), entries.get(1)); EntityView entityView = saveAndCreateEntityView(deviceId, List.of(LONG_KEY)); entries = tsService.findAll(tenantId, entityView.getId(), queries).get(); Assert.assertEquals(2, entries.size()); Assert.assertEquals(toTsEntry(TS + 25000, new LongDataEntry(LONG_KEY, 5L)), entries.get(0)); - Assert.assertEquals(toTsEntry(TS + 75000, new LongDataEntry(LONG_KEY, 5L)), entries.get(1)); + Assert.assertEquals(toTsEntry(TS + 75000 - 1, new LongDataEntry(LONG_KEY, 4L)), entries.get(1)); } @Test diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceTimescaleTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceTimescaleTest.java new file mode 100644 index 0000000000..c36934cf15 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/nosql/TimeseriesServiceTimescaleTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.service.timeseries.nosql; + +import org.thingsboard.server.dao.service.DaoTimescaleTest; +import org.thingsboard.server.dao.service.timeseries.BaseTimeseriesServiceTest; + +@DaoTimescaleTest +public class TimeseriesServiceTimescaleTest extends BaseTimeseriesServiceTest { +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/query/EntityDataAdapterTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/query/EntityDataAdapterTest.java index 8965358e63..6d052db1d1 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/query/EntityDataAdapterTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/query/EntityDataAdapterTest.java @@ -25,5 +25,6 @@ public class EntityDataAdapterTest { public void testConvertValue() { assertThat(EntityDataAdapter.convertValue("500")).isEqualTo("500"); assertThat(EntityDataAdapter.convertValue("500D")).isEqualTo("500D"); //do not convert to Double !!! + assertThat(EntityDataAdapter.convertValue("0101010521130565")).isEqualTo("0101010521130565"); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDaoTest.java index db216f7f6e..9c4b1cbbea 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDaoTest.java @@ -19,8 +19,11 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.test.util.ReflectionTestUtils; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.common.data.kv.TsKvEntry; import java.util.Optional; @@ -43,25 +46,25 @@ public class AbstractChunkedAggregationTimeseriesDaoTest { final int LIMIT = 1; final String TEMP = "temp"; final String DESC = "DESC"; - AbstractChunkedAggregationTimeseriesDao tsDao; + private AbstractChunkedAggregationTimeseriesDao tsDao; @Before public void setUp() throws Exception { tsDao = spy(AbstractChunkedAggregationTimeseriesDao.class); - ListenableFuture> optionalListenableFuture = Futures.immediateFuture(Optional.of(mock(TsKvEntry.class))); - willReturn(optionalListenableFuture).given(tsDao).findAndAggregateAsync(any(), anyString(), anyLong(), anyLong(), anyLong(), any()); - willReturn(Futures.immediateFuture(mock(TsKvEntry.class))).given(tsDao).getTskvEntriesFuture(any()); + Optional optionalListenableFuture = Optional.of(mock(TsKvEntry.class)); + willReturn(Futures.immediateFuture(optionalListenableFuture)).given(tsDao).findAndAggregateAsync(any(), anyString(), anyLong(), anyLong(), anyLong(), any()); + willReturn(Futures.immediateFuture(mock(ReadTsKvQueryResult.class))).given(tsDao).getReadTsKvQueryResultFuture(any(), any()); } @Test public void givenIntervalNotMultiplePeriod_whenAggregateCount_thenLastIntervalShorterThanOthersAndEqualsEndTs() { ReadTsKvQuery query = new BaseReadTsKvQuery(TEMP, 1, 3000, 2000, LIMIT, COUNT, DESC); ReadTsKvQuery subQueryFirst = new BaseReadTsKvQuery(TEMP, 1, 2001, 1001, LIMIT, COUNT, DESC); - ReadTsKvQuery subQuerySecond = new BaseReadTsKvQuery(TEMP, 2001, 3001, 2501, LIMIT, COUNT, DESC); + ReadTsKvQuery subQuerySecond = new BaseReadTsKvQuery(TEMP, 2001, 3000, 2501, LIMIT, COUNT, DESC); tsDao.findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); verify(tsDao, times(2)).findAndAggregateAsync(any(), any(), anyLong(), anyLong(), anyLong(), any()); verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQueryFirst.getKey(), 1, 2001, getTsForReadTsKvQuery(1, 2001), COUNT); - verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQuerySecond.getKey(), 2001, 3000 + 1, getTsForReadTsKvQuery(2001, 3001), COUNT); + verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQuerySecond.getKey(), 2001, 3000, getTsForReadTsKvQuery(2001, 3000), COUNT); } @Test @@ -71,19 +74,17 @@ public class AbstractChunkedAggregationTimeseriesDaoTest { willCallRealMethod().given(tsDao).findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); assertThat(tsDao.findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query)).isNotNull(); verify(tsDao, times(1)).findAndAggregateAsync(any(), any(), anyLong(), anyLong(), anyLong(), any()); - verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQueryFirst.getKey(), 1, 3000 + 1, getTsForReadTsKvQuery(1, 3001), COUNT); + verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQueryFirst.getKey(), 1, 3000, getTsForReadTsKvQuery(1, 3000), COUNT); } @Test public void givenIntervalNotMultiplePeriod_whenAggregateCount_thenIntervalEqualsPeriodMinusOne() { ReadTsKvQuery query = new BaseReadTsKvQuery(TEMP, 1, 3000, 2999, LIMIT, COUNT, DESC); ReadTsKvQuery subQueryFirst = new BaseReadTsKvQuery(TEMP, 1, 3000, 1500, LIMIT, COUNT, DESC); - ReadTsKvQuery subQuerySecond = new BaseReadTsKvQuery(TEMP, 3000, 3001, 3000, LIMIT, COUNT, DESC); willCallRealMethod().given(tsDao).findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); tsDao.findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); - verify(tsDao, times(2)).findAndAggregateAsync(any(), any(), anyLong(), anyLong(), anyLong(), any()); + verify(tsDao, times(1)).findAndAggregateAsync(any(), any(), anyLong(), anyLong(), anyLong(), any()); verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQueryFirst.getKey(), 1, 3000, getTsForReadTsKvQuery(1, 3000), COUNT); - verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQuerySecond.getKey(), 3000, 3001, getTsForReadTsKvQuery(3000, 3001), COUNT); } @@ -94,7 +95,7 @@ public class AbstractChunkedAggregationTimeseriesDaoTest { willCallRealMethod().given(tsDao).findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); tsDao.findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); verify(tsDao, times(1)).findAndAggregateAsync(any(), any(), anyLong(), anyLong(), anyLong(), any()); - verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQueryFirst.getKey(), 1, 3001, getTsForReadTsKvQuery(1, 3001), COUNT); + verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQueryFirst.getKey(), 1, 3000, getTsForReadTsKvQuery(1, 3000), COUNT); } @Test @@ -134,7 +135,7 @@ public class AbstractChunkedAggregationTimeseriesDaoTest { willCallRealMethod().given(tsDao).findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); tsDao.findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); verify(tsDao, times(1)).findAndAggregateAsync(any(), any(), anyLong(), anyLong(), anyLong(), any()); - verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQueryFirst.getKey(), 1, 3001, getTsForReadTsKvQuery(1, 3001), COUNT); + verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQueryFirst.getKey(), 1, 3000, getTsForReadTsKvQuery(1, 3000), COUNT); } @Test @@ -144,8 +145,7 @@ public class AbstractChunkedAggregationTimeseriesDaoTest { tsDao.findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); verify(tsDao, times(1000)).findAndAggregateAsync(any(), any(), anyLong(), anyLong(), anyLong(), any()); for (long i = 1; i <= 3000; i += 3) { - ReadTsKvQuery querySub = new BaseReadTsKvQuery(TEMP, i, i + 3, i + (i + 3 - i) / 2, LIMIT, COUNT, DESC); - verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, querySub.getKey(), i, i + 3, getTsForReadTsKvQuery(i, i + 3), COUNT); + verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, TEMP, i, Math.min(i + 3, 3000), getTsForReadTsKvQuery(i, i + 3), COUNT); } } diff --git a/dao/src/test/resources/timescale-test.properties b/dao/src/test/resources/timescale-test.properties new file mode 100644 index 0000000000..2c5552cb75 --- /dev/null +++ b/dao/src/test/resources/timescale-test.properties @@ -0,0 +1,18 @@ +database.ts.type=timescale +database.ts_latest.type=timescale + +sql.ts_inserts_executor_type=fixed +sql.ts_inserts_fixed_thread_pool_size=10 + +spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true +spring.jpa.properties.hibernate.order_by.default_null_ordering=last +spring.jpa.properties.hibernate.jdbc.log.warnings=false + +spring.jpa.show-sql=false + +spring.jpa.hibernate.ddl-auto=none +spring.datasource.username=postgres +spring.datasource.password=postgres +spring.datasource.url=jdbc:tc:timescaledb:latest-pg12:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.TimescaleSqlInitializer::initDb +spring.datasource.driverClassName=org.testcontainers.jdbc.ContainerDatabaseDriver +spring.datasource.hikari.maximumPoolSize = 50 diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index da4e900015..a1c745e163 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java index bfe1c913eb..07529baead 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.msa; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableMap; @@ -24,20 +23,16 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import lombok.extern.slf4j.Slf4j; -import org.apache.cassandra.cql3.Json; -import org.apache.commons.lang3.RandomStringUtils; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustStrategy; -import org.apache.http.conn.ssl.X509HostnameVerifier; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.ssl.SSLContexts; -import org.json.simple.JSONObject; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.rules.TestRule; @@ -47,20 +42,14 @@ import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.thingsboard.rest.client.RestClient; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.msa.mapper.WsTelemetryResponse; - -import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocket; import java.net.URI; -import java.security.cert.X509Certificate; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Random; @Slf4j @@ -125,7 +114,7 @@ public abstract class AbstractContainerTest { protected Device createDevice(String name) { Device device = new Device(); - device.setName(name + RandomStringUtils.randomAlphanumeric(7)); + device.setName(name + StringUtils.randomAlphanumeric(7)); device.setType("DEFAULT"); return restClient.saveDevice(device); } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java index eedf070fac..979fbb187d 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java @@ -16,9 +16,9 @@ package org.thingsboard.server.msa; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.rules.ExternalResource; import org.testcontainers.utility.Base58; +import org.thingsboard.server.common.data.StringUtils; import java.io.File; import java.util.ArrayList; @@ -215,7 +215,7 @@ public class ThingsBoardDbInstaller extends ExternalResource { File tbLogsDir = new File(targetDir); tbLogsDir.mkdirs(); - String logsContainerName = "tb-logs-container-" + RandomStringUtils.randomAlphanumeric(10); + String logsContainerName = "tb-logs-container-" + StringUtils.randomAlphanumeric(10); dockerCompose.withCommand("run -d --rm --name " + logsContainerName + " -v " + volumeName + ":/root alpine tail -f /dev/null"); dockerCompose.invokeDocker(); diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java index 19a53cb1ce..ce36f08a64 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java @@ -26,8 +26,8 @@ import io.netty.buffer.Unpooled; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.Data; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; -import org.junit.*; +import org.junit.Assert; +import org.junit.Test; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; @@ -36,6 +36,7 @@ import org.thingsboard.mqtt.MqttClient; import org.thingsboard.mqtt.MqttClientConfig; import org.thingsboard.mqtt.MqttHandler; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.rule.NodeConnectionInfo; @@ -50,8 +51,15 @@ import org.thingsboard.server.msa.mapper.WsTelemetryResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.*; +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; +import java.util.Random; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; @Slf4j public class MqttClientTest extends AbstractContainerTest { @@ -150,7 +158,7 @@ public class MqttClientTest extends AbstractContainerTest { // Add a new client attribute JsonObject clientAttributes = new JsonObject(); - String clientAttributeValue = RandomStringUtils.randomAlphanumeric(8); + String clientAttributeValue = StringUtils.randomAlphanumeric(8); clientAttributes.addProperty("clientAttr", clientAttributeValue); mqttClient.publish("v1/devices/me/attributes", Unpooled.wrappedBuffer(clientAttributes.toString().getBytes())).get(); @@ -166,7 +174,7 @@ public class MqttClientTest extends AbstractContainerTest { // Add a new shared attribute JsonObject sharedAttributes = new JsonObject(); - String sharedAttributeValue = RandomStringUtils.randomAlphanumeric(8); + String sharedAttributeValue = StringUtils.randomAlphanumeric(8); sharedAttributes.addProperty("sharedAttr", sharedAttributeValue); ResponseEntity sharedAttributesResponse = restClient.getRestTemplate() .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", @@ -215,7 +223,7 @@ public class MqttClientTest extends AbstractContainerTest { // Add a new shared attribute JsonObject sharedAttributes = new JsonObject(); - String sharedAttributeValue = RandomStringUtils.randomAlphanumeric(8); + String sharedAttributeValue = StringUtils.randomAlphanumeric(8); sharedAttributes.addProperty(sharedAttributeName, sharedAttributeValue); ResponseEntity sharedAttributesResponse = restClient.getRestTemplate() .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", @@ -229,7 +237,7 @@ public class MqttClientTest extends AbstractContainerTest { // Update the shared attribute value JsonObject updatedSharedAttributes = new JsonObject(); - String updatedSharedAttributeValue = RandomStringUtils.randomAlphanumeric(8); + String updatedSharedAttributeValue = StringUtils.randomAlphanumeric(8); updatedSharedAttributes.addProperty(sharedAttributeName, updatedSharedAttributeValue); ResponseEntity updatedSharedAttributesResponse = restClient.getRestTemplate() .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java index 46aa4acd1d..25a8946431 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java @@ -28,7 +28,6 @@ import io.netty.buffer.Unpooled; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.Data; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -39,6 +38,7 @@ import org.thingsboard.mqtt.MqttClient; import org.thingsboard.mqtt.MqttClientConfig; import org.thingsboard.mqtt.MqttHandler; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.relation.EntityRelation; @@ -226,7 +226,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { WsClient wsClient = subscribeToWebSocket(createdDevice.getId(), "CLIENT_SCOPE", CmdsType.ATTR_SUB_CMDS); // Add a new client attribute JsonObject clientAttributes = new JsonObject(); - String clientAttributeValue = RandomStringUtils.randomAlphanumeric(8); + String clientAttributeValue = StringUtils.randomAlphanumeric(8); clientAttributes.addProperty("clientAttr", clientAttributeValue); JsonObject gatewayClientAttributes = new JsonObject(); @@ -245,7 +245,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { // Add a new shared attribute JsonObject sharedAttributes = new JsonObject(); - String sharedAttributeValue = RandomStringUtils.randomAlphanumeric(8); + String sharedAttributeValue = StringUtils.randomAlphanumeric(8); sharedAttributes.addProperty("sharedAttr", sharedAttributeValue); // Subscribe for attribute update event @@ -281,7 +281,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { // Add a new shared attribute JsonObject sharedAttributes = new JsonObject(); - String sharedAttributeValue = RandomStringUtils.randomAlphanumeric(8); + String sharedAttributeValue = StringUtils.randomAlphanumeric(8); sharedAttributes.addProperty(sharedAttributeName, sharedAttributeValue); JsonObject gatewaySharedAttributeValue = new JsonObject(); @@ -300,7 +300,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { // Update the shared attribute value JsonObject updatedSharedAttributes = new JsonObject(); - String updatedSharedAttributeValue = RandomStringUtils.randomAlphanumeric(8); + String updatedSharedAttributeValue = StringUtils.randomAlphanumeric(8); updatedSharedAttributes.addProperty(sharedAttributeName, updatedSharedAttributeValue); JsonObject gatewayUpdatedSharedAttributeValue = new JsonObject(); diff --git a/msa/js-executor/docker/Dockerfile b/msa/js-executor/docker/Dockerfile index 8746e1db54..93e5ab21ff 100644 --- a/msa/js-executor/docker/Dockerfile +++ b/msa/js-executor/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/node:16.15.1-bullseye-slim +FROM thingsboard/node:16.17.0-bullseye-slim ENV NODE_ENV production ENV DOCKER_MODE true diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index f220fd4ff5..dea0eb29c1 100644 --- a/msa/js-executor/package.json +++ b/msa/js-executor/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-js-executor", "private": true, - "version": "3.4.1", + "version": "3.4.2", "description": "ThingsBoard JavaScript Executor Microservice", "main": "server.ts", "bin": "server.js", diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index b13be7048e..95a4e0c724 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/pom.xml b/msa/pom.xml index 4de30d5fdf..e527566683 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT thingsboard msa diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index 785748957e..965d0ab05a 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index 1226f5a5ee..30358d97cb 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml index 0fc536f512..3b5a91dd13 100644 --- a/msa/transport/coap/pom.xml +++ b/msa/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml index 00f3b19917..6fbb13f32f 100644 --- a/msa/transport/http/pom.xml +++ b/msa/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/lwm2m/pom.xml b/msa/transport/lwm2m/pom.xml index 746df2afc4..fa8d99e8b5 100644 --- a/msa/transport/lwm2m/pom.xml +++ b/msa/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml index da786c014f..c63fd43668 100644 --- a/msa/transport/mqtt/pom.xml +++ b/msa/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index efc9093263..38f08cffaf 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/snmp/pom.xml b/msa/transport/snmp/pom.xml index e2ec3d811d..8edd6dfeaa 100644 --- a/msa/transport/snmp/pom.xml +++ b/msa/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.msa transport - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT org.thingsboard.msa.transport diff --git a/msa/vc-executor-docker/pom.xml b/msa/vc-executor-docker/pom.xml index c4be5c06da..38c6979c83 100644 --- a/msa/vc-executor-docker/pom.xml +++ b/msa/vc-executor-docker/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/vc-executor/pom.xml b/msa/vc-executor/pom.xml index bc315e8d34..2a4d07011e 100644 --- a/msa/vc-executor/pom.xml +++ b/msa/vc-executor/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/web-ui/docker/Dockerfile b/msa/web-ui/docker/Dockerfile index 7b047381ae..af6b2d5de6 100644 --- a/msa/web-ui/docker/Dockerfile +++ b/msa/web-ui/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/node:16.15.1-bullseye-slim +FROM thingsboard/node:16.17.0-bullseye-slim ENV NODE_ENV production ENV DOCKER_MODE true diff --git a/msa/web-ui/package.json b/msa/web-ui/package.json index 797ac1c9fd..4529d51510 100644 --- a/msa/web-ui/package.json +++ b/msa/web-ui/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-web-ui", "private": true, - "version": "3.4.1", + "version": "3.4.2", "description": "ThingsBoard Web UI Microservice", "main": "server.ts", "bin": "server.js", diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index 9d9f3f8e08..872170798f 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT msa org.thingsboard.msa diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index 472ce55fc7..5b4c8506c6 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -19,11 +19,11 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT thingsboard netty-mqtt - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT jar Netty MQTT Client diff --git a/pom.xml b/pom.xml index 85934a5178..347874faef 100755 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT pom Thingsboard @@ -89,7 +89,7 @@ 1.6.3 0.7 1.18.2 - 1.67 + 1.69 2.0.1 42.2.20 org/thingsboard/server/gen/**/*, @@ -133,7 +133,7 @@ 1.3.0 1.2.7 - 1.16.0 + 1.17.3 1.12 3.0.0 6.1.0.202203080745-r diff --git a/rest-client/pom.xml b/rest-client/pom.xml index 8b1d4b9641..4b9efe6177 100644 --- a/rest-client/pom.xml +++ b/rest-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT thingsboard rest-client diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index f2f446e534..c1402b93ad 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -34,7 +34,7 @@ import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.support.HttpRequestWrapper; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import org.thingsboard.common.util.ThingsBoardExecutors; diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index 584bce461c..438c2d3157 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT thingsboard rule-engine diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index f3ce720866..99170b585c 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNode.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNode.java index 8780833a5e..1516b6957c 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNode.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNode.java @@ -29,7 +29,7 @@ public interface TbNode { void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException; - void destroy(); + default void destroy() {} default void onPartitionChangeMsg(TbContext ctx, PartitionChangeMsg msg) {} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java index bc38db7fd6..940a5453b6 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java @@ -19,9 +19,9 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index ee32658d63..439ed41859 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNode.java index 0cd637c01c..efff637e57 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNode.java @@ -92,6 +92,9 @@ public abstract class TbAbstractCustomerActionNode processEntityRelationAction(TbContext ctx, TbMsg msg, String relationType) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index 73e03892c9..fd69303501 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -158,7 +158,4 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { return CollectionsUtil.contains(keys, attrKey); } - @Override - public void destroy() { - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java index 22bed5b4e4..2d34972bfa 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java @@ -98,7 +98,4 @@ public class TbMsgCountNode implements TbNode { ctx.tellSelf(tickMsg, curDelay); } - @Override - public void destroy() { - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java index a03d853c09..06889bd6de 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java @@ -25,7 +25,7 @@ import com.amazonaws.services.sqs.model.SendMessageRequest; import com.amazonaws.services.sqs.model.SendMessageResult; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/CertPemCredentials.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/CertPemCredentials.java index 3a38a286f2..528840d972 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/CertPemCredentials.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/credentials/CertPemCredentials.java @@ -28,7 +28,7 @@ import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; import org.bouncycastle.util.encoders.Hex; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import javax.crypto.Cipher; import javax.crypto.EncryptedPrivateKeyInfo; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index 80b319e087..32ec34ef39 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -19,7 +19,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.common.util.TbStopWatch; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.ScriptEngine; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java index eb30a236af..28dc64c068 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java @@ -71,10 +71,6 @@ public abstract class AbstractTbMsgPushNode dataMap = dataToMap(msg); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java index c4bc9641a4..4832a0af35 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java @@ -103,8 +103,4 @@ public class TbCheckRelationNode implements TbNode { } } - @Override - public void destroy() { - - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java index fd292be43e..aeddcd18d3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java @@ -52,8 +52,4 @@ public class TbMsgTypeFilterNode implements TbNode { ctx.tellNext(msg, config.getMessageTypes().contains(msg.getType()) ? "True" : "False"); } - @Override - public void destroy() { - - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java index 696212c9cb..62e44bb50e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java @@ -117,8 +117,4 @@ public class TbMsgTypeSwitchNode implements TbNode { ctx.tellNext(msg, relationType); } - @Override - public void destroy() { - - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java index 8b893c0396..10ccc77dc1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java @@ -51,8 +51,4 @@ public class TbOriginatorTypeFilterNode implements TbNode { ctx.tellNext(msg, config.getOriginatorTypes().contains(originatorType) ? "True" : "False"); } - @Override - public void destroy() { - - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java index d44c555f8c..e5c06c3878 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeSwitchNode.java @@ -90,8 +90,4 @@ public class TbOriginatorTypeSwitchNode implements TbNode { ctx.tellNext(msg, relationType); } - @Override - public void destroy() { - - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbAckNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbAckNode.java index 801a31fbb6..64938092f6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbAckNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbAckNode.java @@ -51,7 +51,4 @@ public class TbAckNode implements TbNode { ctx.tellSuccess(msg); } - @Override - public void destroy() { - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java index 3fa2b5d64e..ececf96303 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbCheckpointNode.java @@ -54,7 +54,4 @@ public class TbCheckpointNode implements TbNode { ctx.enqueueForTellNext(msg, queueName, TbRelationTypes.SUCCESS, () -> ctx.ack(msg), error -> ctx.tellFailure(msg, error)); } - @Override - public void destroy() { - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainInputNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainInputNode.java index eb2e6bb249..fc4a1edfc9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainInputNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainInputNode.java @@ -63,7 +63,4 @@ public class TbRuleChainInputNode implements TbNode { ctx.input(msg, ruleChainId); } - @Override - public void destroy() { - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainOutputNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainOutputNode.java index 5b014d8bc4..bc701dd56a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainOutputNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/flow/TbRuleChainOutputNode.java @@ -51,7 +51,4 @@ public class TbRuleChainOutputNode implements TbNode { ctx.output(msg, ctx.getSelf().getName()); } - @Override - public void destroy() { - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java index 40bbd6f506..af35e88b81 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/AbstractGeofencingNode.java @@ -148,9 +148,4 @@ public abstract class AbstractGeofencingNode findEntityIdAsync(TbContext ctx, TbMsg msg); private void safePutAttributes(TbContext ctx, TbMsg msg, T entityId) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java index 75d1869e6d..490a42a72c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java @@ -63,10 +63,6 @@ public abstract class TbAbstractGetEntityDetailsNode ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); } - @Override - public void destroy() { - } - protected abstract C loadGetEntityDetailsNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException; protected abstract ListenableFuture getDetails(TbContext ctx, TbMsg msg); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java index b1eaea0bbd..28fd920264 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java @@ -100,11 +100,6 @@ public abstract class TbEntityGetAttrNode implements TbNode ctx.tellSuccess(msg); } - @Override - public void destroy() { - - } - protected abstract ListenableFuture findEntityAsync(TbContext ctx, EntityId originator); public void setConfig(TbGetEntityAttrNodeConfiguration config) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsConfiguration.java index be8e356b59..b0e9bf39e5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetOriginatorFieldsConfiguration.java @@ -25,6 +25,7 @@ import java.util.Map; public class TbGetOriginatorFieldsConfiguration implements NodeConfiguration { private Map fieldsMapping; + private boolean ignoreNullStrings; @Override public TbGetOriginatorFieldsConfiguration defaultConfiguration() { @@ -33,6 +34,7 @@ public class TbGetOriginatorFieldsConfiguration implements NodeConfiguration { config.getFieldsMapping().forEach((field, metaKey) -> { - String val = data.getFieldValue(field); + String val = data.getFieldValue(field, ignoreNullStrings); if (val != null) { msg.getMetaData().putValue(metaKey, val); } @@ -80,8 +82,4 @@ public class TbGetOriginatorFieldsNode implements TbNode { } } - @Override - public void destroy() { - - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java index 320502005b..77298cba77 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java @@ -25,7 +25,7 @@ import com.google.gson.JsonParseException; import lombok.Data; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.rule.engine.api.RuleNode; @@ -98,7 +98,7 @@ public class TbGetTelemetryNode implements TbNode { } Aggregation parseAggregationConfig(String aggName) { - if (StringUtils.isEmpty(aggName)) { + if (StringUtils.isEmpty(aggName) || !fetchMode.equals(FETCH_MODE_ALL)) { return Aggregation.NONE; } return Aggregation.valueOf(aggName); @@ -110,11 +110,9 @@ public class TbGetTelemetryNode implements TbNode { ctx.tellFailure(msg, new IllegalStateException("Telemetry is not selected!")); } else { try { - if (config.isUseMetadataIntervalPatterns()) { - checkMetadataKeyPatterns(msg); - } + Interval interval = getInterval(msg); List keys = TbNodeUtils.processPatterns(tsKeyNames, msg); - ListenableFuture> list = ctx.getTimeseriesService().findAll(ctx.getTenantId(), msg.getOriginator(), buildQueries(msg, keys)); + ListenableFuture> list = ctx.getTimeseriesService().findAll(ctx.getTenantId(), msg.getOriginator(), buildQueries(interval, keys)); DonAsynchron.withCallback(list, data -> { process(data, msg, keys); ctx.tellSuccess(ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), msg.getData())); @@ -125,12 +123,7 @@ public class TbGetTelemetryNode implements TbNode { } } - @Override - public void destroy() { - } - - private List buildQueries(TbMsg msg, List keys) { - final Interval interval = getInterval(msg); + private List buildQueries(Interval interval, List keys) { final long aggIntervalStep = Aggregation.NONE.equals(aggregation) ? 1 : // exact how it validates on BaseTimeseriesService.validate() // see CassandraBaseTimeseriesDao.findAllAsync() @@ -210,71 +203,45 @@ public class TbGetTelemetryNode implements TbNode { } private Interval getInterval(TbMsg msg) { - Interval interval = new Interval(); if (config.isUseMetadataIntervalPatterns()) { - if (isParsable(msg, config.getStartIntervalPattern())) { - interval.setStartTs(Long.parseLong(TbNodeUtils.processPattern(config.getStartIntervalPattern(), msg))); - } - if (isParsable(msg, config.getEndIntervalPattern())) { - interval.setEndTs(Long.parseLong(TbNodeUtils.processPattern(config.getEndIntervalPattern(), msg))); - } + return getIntervalFromPatterns(msg); } else { + Interval interval = new Interval(); long ts = System.currentTimeMillis(); interval.setStartTs(ts - TimeUnit.valueOf(config.getStartIntervalTimeUnit()).toMillis(config.getStartInterval())); interval.setEndTs(ts - TimeUnit.valueOf(config.getEndIntervalTimeUnit()).toMillis(config.getEndInterval())); + return interval; } - return interval; - } - - private boolean isParsable(TbMsg msg, String pattern) { - return NumberUtils.isParsable(TbNodeUtils.processPattern(pattern, msg)); } - private void checkMetadataKeyPatterns(TbMsg msg) { - isUndefined(msg, config.getStartIntervalPattern(), config.getEndIntervalPattern()); - isInvalid(msg, config.getStartIntervalPattern(), config.getEndIntervalPattern()); + private Interval getIntervalFromPatterns(TbMsg msg) { + Interval interval = new Interval(); + interval.setStartTs(checkPattern(msg, config.getStartIntervalPattern())); + interval.setEndTs(checkPattern(msg, config.getEndIntervalPattern())); + return interval; } - private void isUndefined(TbMsg msg, String startIntervalPattern, String endIntervalPattern) { - if (getMetadataValue(msg, startIntervalPattern) == null && getMetadataValue(msg, endIntervalPattern) == null) { - throw new IllegalArgumentException("Message metadata values: '" + - replaceRegex(startIntervalPattern) + "' and '" + - replaceRegex(endIntervalPattern) + "' are undefined"); - } else { - if (getMetadataValue(msg, startIntervalPattern) == null) { - throw new IllegalArgumentException("Message metadata value: '" + - replaceRegex(startIntervalPattern) + "' is undefined"); - } - if (getMetadataValue(msg, endIntervalPattern) == null) { - throw new IllegalArgumentException("Message metadata value: '" + - replaceRegex(endIntervalPattern) + "' is undefined"); - } + private long checkPattern(TbMsg msg, String pattern) { + String value = getValuePattern(msg, pattern); + if (value == null) { + throw new IllegalArgumentException("Message value: '" + + replaceRegex(pattern) + "' is undefined"); } - } - - private void isInvalid(TbMsg msg, String startIntervalPattern, String endIntervalPattern) { - if (getInterval(msg).getStartTs() == null && getInterval(msg).getEndTs() == null) { - throw new IllegalArgumentException("Message metadata values: '" + - replaceRegex(startIntervalPattern) + "' and '" + - replaceRegex(endIntervalPattern) + "' have invalid format"); - } else { - if (getInterval(msg).getStartTs() == null) { - throw new IllegalArgumentException("Message metadata value: '" + - replaceRegex(startIntervalPattern) + "' has invalid format"); - } - if (getInterval(msg).getEndTs() == null) { - throw new IllegalArgumentException("Message metadata value: '" + - replaceRegex(endIntervalPattern) + "' has invalid format"); - } + boolean parsable = NumberUtils.isParsable(value); + if (!parsable) { + throw new IllegalArgumentException("Message value: '" + + replaceRegex(pattern) + "' has invalid format"); } + return Long.parseLong(value); } - private String getMetadataValue(TbMsg msg, String pattern) { - return msg.getMetaData().getValue(replaceRegex(pattern)); + private String getValuePattern(TbMsg msg, String pattern) { + String value = TbNodeUtils.processPattern(pattern, msg); + return value.equals(pattern) ? null : value; } private String replaceRegex(String pattern) { - return pattern.replaceAll("[${}]", ""); + return pattern.replaceAll("[$\\[{}\\]]", ""); } private int validateLimit(int limit) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java index 1600f2271c..6dd632aa97 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java @@ -20,7 +20,7 @@ import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.ssl.SslContext; import io.netty.util.concurrent.Future; import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.mqtt.MqttClient; import org.thingsboard.mqtt.MqttClientConfig; import org.thingsboard.mqtt.MqttConnectResult; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNode.java index 1218518a12..80c75e08ba 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/azure/TbAzureIotHubNode.java @@ -18,7 +18,7 @@ package org.thingsboard.rule.engine.mqtt.azure; import io.netty.handler.codec.mqtt.MqttVersion; import io.netty.handler.ssl.SslContext; import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.common.util.AzureIotHubUtil; import org.thingsboard.mqtt.MqttClientConfig; import org.thingsboard.rule.engine.api.RuleNode; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 831fd3c08c..1f90b4e6c1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -20,7 +20,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.ListenableFuture; import lombok.Data; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.rule.engine.action.TbAlarmResult; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java index f0bbdfb66b..5ab5adc7fc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java @@ -17,7 +17,7 @@ package org.thingsboard.rule.engine.profile; import com.google.gson.JsonParser; import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.profile.state.PersistedAlarmState; import org.thingsboard.rule.engine.profile.state.PersistedDeviceState; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java index 0b3822352e..5ba55fe2d5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java @@ -22,7 +22,7 @@ import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.MessageProperties; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index e035d415ed..28c937c9ce 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -35,7 +35,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.http.client.HttpComponentsAsyncClientHttpRequestFactory; import org.springframework.http.client.Netty4ClientHttpRequestFactory; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.springframework.util.concurrent.ListenableFuture; import org.springframework.util.concurrent.ListenableFutureCallback; import org.springframework.web.client.AsyncRestTemplate; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java index 36497df564..ce9b5050f0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java @@ -16,7 +16,7 @@ package org.thingsboard.rule.engine.rpc; import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; @@ -70,8 +70,4 @@ public class TbSendRPCReplyNode implements TbNode { } } - @Override - public void destroy() { - } - } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java index 45b45e91f9..80d379f79d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java @@ -21,7 +21,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; @@ -127,10 +127,6 @@ public class TbSendRPCRequestNode implements TbNode { } } - @Override - public void destroy() { - } - private String wrap(String name, String body) { JsonObject json = new JsonObject(); json.addProperty(name, body); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRpcReplyNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRpcReplyNodeConfiguration.java index a736759f1a..1a014eb323 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRpcReplyNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRpcReplyNodeConfiguration.java @@ -16,7 +16,7 @@ package org.thingsboard.rule.engine.rpc; import lombok.Data; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.server.common.data.DataConstants; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java index 56e1ca8577..9f1c0d4e20 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java @@ -17,7 +17,7 @@ package org.thingsboard.rule.engine.telemetry; import com.google.gson.JsonParser; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; @@ -75,8 +75,4 @@ public class TbMsgAttributesNode implements TbNode { ); } - @Override - public void destroy() { - } - } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java index 22d9d1ac07..4a44d9f7b1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java @@ -17,7 +17,7 @@ package org.thingsboard.rule.engine.telemetry; import com.google.gson.JsonParser; import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transaction/TbSynchronizationBeginNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transaction/TbSynchronizationBeginNode.java index ad846b6a29..21f1a9d82f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transaction/TbSynchronizationBeginNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transaction/TbSynchronizationBeginNode.java @@ -49,8 +49,4 @@ public class TbSynchronizationBeginNode implements TbNode { ctx.tellSuccess(msg); } - @Override - public void destroy() { - - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transaction/TbSynchronizationEndNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transaction/TbSynchronizationEndNode.java index f346fcf497..5cb81399c6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transaction/TbSynchronizationEndNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transaction/TbSynchronizationEndNode.java @@ -48,7 +48,4 @@ public class TbSynchronizationEndNode implements TbNode { ctx.tellSuccess(msg); } - @Override - public void destroy() { - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java index a59980849d..b48f666184 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java @@ -108,8 +108,4 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode { } - @Override - public void destroy() { - - } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeTest.java index bb086af15a..9c97f6f85b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeTest.java @@ -15,8 +15,12 @@ */ package org.thingsboard.rule.engine.metadata; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.server.common.data.kv.Aggregation; import static org.hamcrest.CoreMatchers.is; @@ -24,14 +28,25 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.willCallRealMethod; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; public class TbGetTelemetryNodeTest { TbGetTelemetryNode node; + TbGetTelemetryNodeConfiguration config; + TbNodeConfiguration nodeConfiguration; + TbContext ctx; @Before public void setUp() throws Exception { - node = mock(TbGetTelemetryNode.class); + ctx = mock(TbContext.class); + node = spy(new TbGetTelemetryNode()); + config = new TbGetTelemetryNodeConfiguration(); + config.setFetchMode("ALL"); + ObjectMapper mapper = JacksonUtil.OBJECT_MAPPER; + nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); + node.init(ctx, nodeConfiguration); + willCallRealMethod().given(node).parseAggregationConfig(any()); } diff --git a/tools/pom.xml b/tools/pom.xml index 1b1f99d44e..850485ed42 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT thingsboard tools diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/DictionaryParser.java b/tools/src/main/java/org/thingsboard/client/tools/migrator/DictionaryParser.java index b6a1be84b1..2feaf7cd77 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/migrator/DictionaryParser.java +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/DictionaryParser.java @@ -17,7 +17,7 @@ package org.thingsboard.client.tools.migrator; import org.apache.commons.io.FileUtils; import org.apache.commons.io.LineIterator; -import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import java.io.File; import java.io.IOException; diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaMigrator.java b/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaMigrator.java index 695356ed2d..77fc0837ed 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaMigrator.java +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaMigrator.java @@ -19,7 +19,7 @@ import com.google.common.collect.Lists; import org.apache.cassandra.io.sstable.CQLSSTableWriter; import org.apache.commons.io.FileUtils; import org.apache.commons.io.LineIterator; -import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import java.io.File; diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/RelatedEntitiesParser.java b/tools/src/main/java/org/thingsboard/client/tools/migrator/RelatedEntitiesParser.java index b0f786a2d3..c84020b6b5 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/migrator/RelatedEntitiesParser.java +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/RelatedEntitiesParser.java @@ -17,7 +17,7 @@ package org.thingsboard.client.tools.migrator; import org.apache.commons.io.FileUtils; import org.apache.commons.io.LineIterator; -import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.EntityType; import java.io.File; diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index 4235f1b1f5..dd151dc920 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 11f4e91141..ffed38e79a 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -115,7 +115,7 @@ transport: key_password: "${COAP_DTLS_PEM_KEY_PASSWORD:server_key_password}" # Keystore server credentials keystore: - # Type of the key store + # Type of the key store (JKS or PKCS12) type: "${COAP_DTLS_KEY_STORE_TYPE:JKS}" # Path to the key store that holds the SSL certificate store_file: "${COAP_DTLS_KEY_STORE:coapserver.jks}" diff --git a/transport/http/pom.xml b/transport/http/pom.xml index 69ab0495c8..15a9305c76 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index 79efbd0a3d..01fe269739 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -39,7 +39,7 @@ server: key_password: "${SSL_PEM_KEY_PASSWORD:server_key_password}" # Keystore server credentials keystore: - # Type of the key store + # Type of the key store (JKS or PKCS12) type: "${SSL_KEY_STORE_TYPE:PKCS12}" # Path to the key store that holds the SSL certificate store_file: "${SSL_KEY_STORE:classpath:keystore/keystore.p12}" diff --git a/transport/lwm2m/pom.xml b/transport/lwm2m/pom.xml index 29fd3c6fb0..b2d7809f4d 100644 --- a/transport/lwm2m/pom.xml +++ b/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 0f28100385..33a8496941 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -129,7 +129,7 @@ transport: key_password: "${LWM2M_SERVER_PEM_KEY_PASSWORD:server_key_password}" # Keystore server credentials keystore: - # Type of the key store + # Type of the key store (JKS or PKCS12) type: "${LWM2M_SERVER_KEY_STORE_TYPE:JKS}" # Path to the key store that holds the SSL certificate store_file: "${LWM2M_SERVER_KEY_STORE:lwm2mserver.jks}" @@ -165,7 +165,7 @@ transport: key_password: "${LWM2M_BS_PEM_KEY_PASSWORD:server_key_password}" # Keystore server credentials keystore: - # Type of the key store + # Type of the key store (JKS or PKCS12) type: "${LWM2M_BS_KEY_STORE_TYPE:JKS}" # Path to the key store that holds the SSL certificate store_file: "${LWM2M_BS_KEY_STORE:lwm2mserver.jks}" @@ -188,7 +188,7 @@ transport: cert_file: "${LWM2M_TRUST_PEM_CERT:lwm2mtruststorechain.pem}" # Keystore with trust certificates keystore: - # Type of the key store + # Type of the key store (JKS or PKCS12) type: "${LWM2M_TRUST_KEY_STORE_TYPE:JKS}" # Path to the key store that holds the X509 certificates store_file: "${LWM2M_TRUST_KEY_STORE:lwm2mtruststorechain.jks}" diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index 77b4d2ad33..4115434335 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index 68a1f71f64..e45f5c29fc 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -125,7 +125,7 @@ transport: key_password: "${MQTT_SSL_PEM_KEY_PASSWORD:server_key_password}" # Keystore server credentials keystore: - # Type of the key store + # Type of the key store (JKS or PKCS12) type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}" # Path to the key store that holds the SSL certificate store_file: "${MQTT_SSL_KEY_STORE:mqttserver.jks}" diff --git a/transport/pom.xml b/transport/pom.xml index c114c031d2..d46c850c41 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT thingsboard transport diff --git a/transport/snmp/pom.xml b/transport/snmp/pom.xml index 7fefe9f49b..57cfd4d8f0 100644 --- a/transport/snmp/pom.xml +++ b/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT transport diff --git a/ui-ngx/package.json b/ui-ngx/package.json index e99ee83549..ea410747b2 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -1,6 +1,6 @@ { "name": "thingsboard", - "version": "3.4.1", + "version": "3.4.2", "scripts": { "ng": "ng", "start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --configuration development --host 0.0.0.0 --open", @@ -29,7 +29,7 @@ "@date-io/date-fns": "^2.11.0", "@flowjs/flow.js": "^2.14.1", "@flowjs/ngx-flow": "~0.4.6", - "@geoman-io/leaflet-geoman-free": "~2.11.4", + "@geoman-io/leaflet-geoman-free": "^2.13.0", "@juggle/resize-observer": "^3.3.1", "@mat-datetimepicker/core": "~7.0.1", "@material-ui/core": "4.12.3", @@ -58,10 +58,10 @@ "jstree": "^3.3.12", "jstree-bootstrap-theme": "^1.0.1", "jszip": "^3.7.1", - "leaflet": "~1.7.1", + "leaflet": "~1.8.0", "leaflet-polylinedecorator": "^1.6.0", "leaflet-providers": "^1.13.0", - "leaflet.gridlayer.googlemutant": "^0.13.4", + "leaflet.gridlayer.googlemutant": "^0.13.5", "leaflet.markercluster": "^1.5.3", "libphonenumber-js": "^1.10.4", "messageformat": "^2.3.0", @@ -115,11 +115,11 @@ "@types/jquery": "^3.5.9", "@types/js-beautify": "^1.13.3", "@types/jstree": "^3.3.41", - "@types/leaflet": "^1.7.6", + "@types/leaflet": "^1.7.11", "@types/leaflet-polylinedecorator": "^1.6.1", "@types/leaflet-providers": "^1.2.1", "@types/leaflet.gridlayer.googlemutant": "^0.4.6", - "@types/leaflet.markercluster": "^1.4.6", + "@types/leaflet.markercluster": "^1.5.1", "@types/lodash": "^4.14.177", "@types/moment-timezone": "^0.5.30", "@types/mousetrap": "^1.6.0", diff --git a/ui-ngx/patches/@geoman-io+leaflet-geoman-free+2.11.4.patch b/ui-ngx/patches/@geoman-io+leaflet-geoman-free+2.11.4.patch deleted file mode 100644 index c079c9ae18..0000000000 --- a/ui-ngx/patches/@geoman-io+leaflet-geoman-free+2.11.4.patch +++ /dev/null @@ -1,21 +0,0 @@ -diff --git a/node_modules/@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css b/node_modules/@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css -index 43ca4d5..2204e44 100644 ---- a/node_modules/@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css -+++ b/node_modules/@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css -@@ -236,7 +236,7 @@ - background-color: #f4f4f4; - } - --.control-icon.pm-disabled { -+.leaflet-buttons-control-button.pm-disabled > .control-icon { - filter: opacity(0.6); - } - -diff --git a/node_modules/@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.min.js b/node_modules/@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.min.js -index 99d226e..e22bac5 100644 ---- a/node_modules/@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.min.js -+++ b/node_modules/@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.min.js -@@ -1 +1 @@ --(()=>{var t={9705:(t,e,n)=>{"use strict";var i=n(1540);function r(t){var e=[Infinity,Infinity,-Infinity,-Infinity];return i.coordEach(t,(function(t){e[0]>t[0]&&(e[0]=t[0]),e[1]>t[1]&&(e[1]=t[1]),e[2]{"use strict";function n(t,e,n){void 0===n&&(n={});var i={type:"Feature"};return(0===n.id||n.id)&&(i.id=n.id),n.bbox&&(i.bbox=n.bbox),i.properties=e||{},i.geometry=t,i}function i(t,e,i){if(void 0===i&&(i={}),!t)throw new Error("coordinates is required");if(!Array.isArray(t))throw new Error("coordinates must be an Array");if(t.length<2)throw new Error("coordinates must be at least 2 numbers long");if(!d(t[0])||!d(t[1]))throw new Error("coordinates must contain numbers");return n({type:"Point",coordinates:t},e,i)}function r(t,e,i){void 0===i&&(i={});for(var r=0,a=t;r=0))throw new Error("precision must be a positive number");var n=Math.pow(10,e||0);return Math.round(t*n)/n},e.radiansToLength=u,e.lengthToRadians=c,e.lengthToDegrees=function(t,e){return p(c(t,e))},e.bearingToAzimuth=function(t){var e=t%360;return e<0&&(e+=360),e},e.radiansToDegrees=p,e.degreesToRadians=function(t){return t%360*Math.PI/180},e.convertLength=function(t,e,n){if(void 0===e&&(e="kilometers"),void 0===n&&(n="kilometers"),!(t>=0))throw new Error("length must be a positive number");return u(c(t,e),n)},e.convertArea=function(t,n,i){if(void 0===n&&(n="meters"),void 0===i&&(i="kilometers"),!(t>=0))throw new Error("area must be a positive number");var r=e.areaFactors[n];if(!r)throw new Error("invalid original units");var a=e.areaFactors[i];if(!a)throw new Error("invalid final units");return t/r*a},e.isNumber=d,e.isObject=function(t){return!!t&&t.constructor===Object},e.validateBBox=function(t){if(!t)throw new Error("bbox is required");if(!Array.isArray(t))throw new Error("bbox must be an Array");if(4!==t.length&&6!==t.length)throw new Error("bbox must be an Array of 4 or 6 numbers");t.forEach((function(t){if(!d(t))throw new Error("bbox must only contain numbers")}))},e.validateId=function(t){if(!t)throw new Error("id is required");if(-1===["string","number"].indexOf(typeof t))throw new Error("id must be a number or a string")}},1540:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0});var i=n(4102);function r(t,e,n){if(null!==t)for(var i,a,o,s,l,h,u,c,p=0,d=0,f=t.type,g="FeatureCollection"===f,_="Feature"===f,m=g?t.features.length:1,y=0;yh||d>u||f>c)return l=r,h=n,u=d,c=f,void(o=0);var g=i.lineString([l,r],t.properties);if(!1===e(g,n,a,f,o))return!1;o++,l=r}))&&void 0}}}))}function u(t,e){if(!t)throw new Error("geojson is required");l(t,(function(t,n,r){if(null!==t.geometry){var a=t.geometry.type,o=t.geometry.coordinates;switch(a){case"LineString":if(!1===e(t,n,r,0,0))return!1;break;case"Polygon":for(var s=0;s{"use strict";n(7107);var i=n(2492),r=n.n(i);const a=JSON.parse('{"tooltips":{"placeMarker":"Click to place marker","firstVertex":"Click to place first vertex","continueLine":"Click to continue drawing","finishLine":"Click any existing marker to finish","finishPoly":"Click first marker to finish","finishRect":"Click to finish","startCircle":"Click to place circle center","finishCircle":"Click to finish circle","placeCircleMarker":"Click to place circle marker"},"actions":{"finish":"Finish","cancel":"Cancel","removeLastVertex":"Remove Last Vertex"},"buttonTitles":{"drawMarkerButton":"Draw Marker","drawPolyButton":"Draw Polygons","drawLineButton":"Draw Polyline","drawCircleButton":"Draw Circle","drawRectButton":"Draw Rectangle","editButton":"Edit Layers","dragButton":"Drag Layers","cutButton":"Cut Layers","deleteButton":"Remove Layers","drawCircleMarkerButton":"Draw Circle Marker","snappingButton":"Snap dragged marker to other layers and vertices","pinningButton":"Pin shared vertices together","rotateButton":"Rotate Layers"}}'),o=JSON.parse('{"tooltips":{"placeMarker":"Platziere den Marker mit Klick","firstVertex":"Platziere den ersten Marker mit Klick","continueLine":"Klicke, um weiter zu zeichnen","finishLine":"Beende mit Klick auf existierenden Marker","finishPoly":"Beende mit Klick auf ersten Marker","finishRect":"Beende mit Klick","startCircle":"Platziere das Kreiszentrum mit Klick","finishCircle":"Beende den Kreis mit Klick","placeCircleMarker":"Platziere den Kreismarker mit Klick"},"actions":{"finish":"Beenden","cancel":"Abbrechen","removeLastVertex":"Letzten Vertex löschen"},"buttonTitles":{"drawMarkerButton":"Marker zeichnen","drawPolyButton":"Polygon zeichnen","drawLineButton":"Polyline zeichnen","drawCircleButton":"Kreis zeichnen","drawRectButton":"Rechteck zeichnen","editButton":"Layer editieren","dragButton":"Layer bewegen","cutButton":"Layer schneiden","deleteButton":"Layer löschen","drawCircleMarkerButton":"Kreismarker zeichnen","snappingButton":"Bewegter Layer an andere Layer oder Vertexe einhacken","pinningButton":"Vertexe an der gleichen Position verknüpfen","rotateButton":"Layer drehen"}}'),s=JSON.parse('{"tooltips":{"placeMarker":"Clicca per posizionare un Marker","firstVertex":"Clicca per posizionare il primo vertice","continueLine":"Clicca per continuare a disegnare","finishLine":"Clicca qualsiasi marker esistente per terminare","finishPoly":"Clicca il primo marker per terminare","finishRect":"Clicca per terminare","startCircle":"Clicca per posizionare il punto centrale del cerchio","finishCircle":"Clicca per terminare il cerchio","placeCircleMarker":"Clicca per posizionare un Marker del cherchio"},"actions":{"finish":"Termina","cancel":"Annulla","removeLastVertex":"Rimuovi l\'ultimo vertice"},"buttonTitles":{"drawMarkerButton":"Disegna Marker","drawPolyButton":"Disegna Poligoni","drawLineButton":"Disegna Polilinea","drawCircleButton":"Disegna Cerchio","drawRectButton":"Disegna Rettangolo","editButton":"Modifica Livelli","dragButton":"Sposta Livelli","cutButton":"Ritaglia Livelli","deleteButton":"Elimina Livelli","drawCircleMarkerButton":"Disegna Marker del Cerchio","snappingButton":"Snap ha trascinato il pennarello su altri strati e vertici","pinningButton":"Pin condiviso vertici insieme"}}'),l=JSON.parse('{"tooltips":{"placeMarker":"Klik untuk menempatkan marker","firstVertex":"Klik untuk menempatkan vertex pertama","continueLine":"Klik untuk meneruskan digitasi","finishLine":"Klik pada sembarang marker yang ada untuk mengakhiri","finishPoly":"Klik marker pertama untuk mengakhiri","finishRect":"Klik untuk mengakhiri","startCircle":"Klik untuk menempatkan titik pusat lingkaran","finishCircle":"Klik untuk mengakhiri lingkaran","placeCircleMarker":"Klik untuk menempatkan penanda lingkarann"},"actions":{"finish":"Selesai","cancel":"Batal","removeLastVertex":"Hilangkan Vertex Terakhir"},"buttonTitles":{"drawMarkerButton":"Digitasi Marker","drawPolyButton":"Digitasi Polygon","drawLineButton":"Digitasi Polyline","drawCircleButton":"Digitasi Lingkaran","drawRectButton":"Digitasi Segi Empat","editButton":"Edit Layer","dragButton":"Geser Layer","cutButton":"Potong Layer","deleteButton":"Hilangkan Layer","drawCircleMarkerButton":"Digitasi Penanda Lingkaran","snappingButton":"Jepretkan penanda yang ditarik ke lapisan dan simpul lain","pinningButton":"Sematkan simpul bersama bersama"}}'),h=JSON.parse('{"tooltips":{"placeMarker":"Adaugă un punct","firstVertex":"Apasă aici pentru a adăuga primul Vertex","continueLine":"Apasă aici pentru a continua desenul","finishLine":"Apasă pe orice obiect pentru a finisa desenul","finishPoly":"Apasă pe primul obiect pentru a finisa","finishRect":"Apasă pentru a finisa","startCircle":"Apasă pentru a desena un cerc","finishCircle":"Apasă pentru a finisa un cerc","placeCircleMarker":"Adaugă un punct"},"actions":{"finish":"Termină","cancel":"Anulează","removeLastVertex":"Șterge ultimul Vertex"},"buttonTitles":{"drawMarkerButton":"Adaugă o bulină","drawPolyButton":"Desenează un poligon","drawLineButton":"Desenează o linie","drawCircleButton":"Desenează un cerc","drawRectButton":"Desenează un dreptunghi","editButton":"Editează straturile","dragButton":"Mută straturile","cutButton":"Taie straturile","deleteButton":"Șterge straturile","drawCircleMarkerButton":"Desenează marcatorul cercului","snappingButton":"Fixați marcatorul glisat pe alte straturi și vârfuri","pinningButton":"Fixați vârfurile partajate împreună"}}'),u=JSON.parse('{"tooltips":{"placeMarker":"Нажмите, чтобы нанести маркер","firstVertex":"Нажмите, чтобы нанести первый объект","continueLine":"Нажмите, чтобы продолжить рисование","finishLine":"Нажмите любой существующий маркер для завершения","finishPoly":"Выберите первую точку, чтобы закончить","finishRect":"Нажмите, чтобы закончить","startCircle":"Нажмите, чтобы добавить центр круга","finishCircle":"Нажмите, чтобы задать радиус","placeCircleMarker":"Нажмите, чтобы нанести круговой маркер"},"actions":{"finish":"Завершить","cancel":"Отменить","removeLastVertex":"Отменить последнее действие"},"buttonTitles":{"drawMarkerButton":"Добавить маркер","drawPolyButton":"Рисовать полигон","drawLineButton":"Рисовать кривую","drawCircleButton":"Рисовать круг","drawRectButton":"Рисовать прямоугольник","editButton":"Редактировать слой","dragButton":"Перенести слой","cutButton":"Вырезать слой","deleteButton":"Удалить слой","drawCircleMarkerButton":"Добавить круговой маркер","snappingButton":"Привязать перетаскиваемый маркер к другим слоям и вершинам","pinningButton":"Связать общие точки вместе"}}'),c=JSON.parse('{"tooltips":{"placeMarker":"Presiona para colocar un marcador","firstVertex":"Presiona para colocar el primer vértice","continueLine":"Presiona para continuar dibujando","finishLine":"Presiona cualquier marcador existente para finalizar","finishPoly":"Presiona el primer marcador para finalizar","finishRect":"Presiona para finalizar","startCircle":"Presiona para colocar el centro del circulo","finishCircle":"Presiona para finalizar el circulo","placeCircleMarker":"Presiona para colocar un marcador de circulo"},"actions":{"finish":"Finalizar","cancel":"Cancelar","removeLastVertex":"Remover ultimo vértice"},"buttonTitles":{"drawMarkerButton":"Dibujar Marcador","drawPolyButton":"Dibujar Polígono","drawLineButton":"Dibujar Línea","drawCircleButton":"Dibujar Circulo","drawRectButton":"Dibujar Rectángulo","editButton":"Editar Capas","dragButton":"Arrastrar Capas","cutButton":"Cortar Capas","deleteButton":"Remover Capas","drawCircleMarkerButton":"Dibujar Marcador de Circulo","snappingButton":"El marcador de Snap arrastrado a otras capas y vértices","pinningButton":"Fijar juntos los vértices compartidos"}}'),p=JSON.parse('{"tooltips":{"placeMarker":"Klik om een marker te plaatsen","firstVertex":"Klik om het eerste punt te plaatsen","continueLine":"Klik om te blijven tekenen","finishLine":"Klik op een bestaand punt om te beëindigen","finishPoly":"Klik op het eerst punt om te beëindigen","finishRect":"Klik om te beëindigen","startCircle":"Klik om het middelpunt te plaatsen","finishCircle":"Klik om de cirkel te beëindigen","placeCircleMarker":"Klik om een marker te plaatsen"},"actions":{"finish":"Bewaar","cancel":"Annuleer","removeLastVertex":"Verwijder laatste punt"},"buttonTitles":{"drawMarkerButton":"Plaats Marker","drawPolyButton":"Teken een vlak","drawLineButton":"Teken een lijn","drawCircleButton":"Teken een cirkel","drawRectButton":"Teken een vierkant","editButton":"Bewerk","dragButton":"Verplaats","cutButton":"Knip","deleteButton":"Verwijder","drawCircleMarkerButton":"Plaats Marker","snappingButton":"Snap gesleepte marker naar andere lagen en hoekpunten","pinningButton":"Speld gedeelde hoekpunten samen"}}'),d=JSON.parse('{"tooltips":{"placeMarker":"Cliquez pour placer un marqueur","firstVertex":"Cliquez pour placer le premier sommet","continueLine":"Cliquez pour continuer à dessiner","finishLine":"Cliquez sur n\'importe quel marqueur pour terminer","finishPoly":"Cliquez sur le premier marqueur pour terminer","finishRect":"Cliquez pour terminer","startCircle":"Cliquez pour placer le centre du cercle","finishCircle":"Cliquez pour finir le cercle","placeCircleMarker":"Cliquez pour placer le marqueur circulaire"},"actions":{"finish":"Terminer","cancel":"Annuler","removeLastVertex":"Retirer le dernier sommet"},"buttonTitles":{"drawMarkerButton":"Placer des marqueurs","drawPolyButton":"Dessiner des polygones","drawLineButton":"Dessiner des polylignes","drawCircleButton":"Dessiner un cercle","drawRectButton":"Dessiner un rectangle","editButton":"Éditer des calques","dragButton":"Déplacer des calques","cutButton":"Couper des calques","deleteButton":"Supprimer des calques","drawCircleMarkerButton":"Dessiner un marqueur circulaire","snappingButton":"Glisser le marqueur vers d\'autres couches et sommets","pinningButton":"Épingler ensemble les sommets partagés","rotateButton":"Tourner des calques"}}'),f=JSON.parse('{"tooltips":{"placeMarker":"单击放置标记","firstVertex":"单击放置首个顶点","continueLine":"单击继续绘制","finishLine":"单击任何存在的标记以完成","finishPoly":"单击第一个标记以完成","finishRect":"单击完成","startCircle":"单击放置圆心","finishCircle":"单击完成圆形","placeCircleMarker":"点击放置圆形标记"},"actions":{"finish":"完成","cancel":"取消","removeLastVertex":"移除最后的顶点"},"buttonTitles":{"drawMarkerButton":"绘制标记","drawPolyButton":"绘制多边形","drawLineButton":"绘制线段","drawCircleButton":"绘制圆形","drawRectButton":"绘制长方形","editButton":"编辑图层","dragButton":"拖拽图层","cutButton":"剪切图层","deleteButton":"删除图层","drawCircleMarkerButton":"画圆圈标记","snappingButton":"将拖动的标记捕捉到其他图层和顶点","pinningButton":"将共享顶点固定在一起"}}'),g=JSON.parse('{"tooltips":{"placeMarker":"單擊放置標記","firstVertex":"單擊放置第一個頂點","continueLine":"單擊繼續繪製","finishLine":"單擊任何存在的標記以完成","finishPoly":"單擊第一個標記以完成","finishRect":"單擊完成","startCircle":"單擊放置圓心","finishCircle":"單擊完成圓形","placeCircleMarker":"點擊放置圓形標記"},"actions":{"finish":"完成","cancel":"取消","removeLastVertex":"移除最後一個頂點"},"buttonTitles":{"drawMarkerButton":"放置標記","drawPolyButton":"繪製多邊形","drawLineButton":"繪製線段","drawCircleButton":"繪製圓形","drawRectButton":"繪製方形","editButton":"編輯圖形","dragButton":"移動圖形","cutButton":"裁切圖形","deleteButton":"刪除圖形","drawCircleMarkerButton":"畫圓圈標記","snappingButton":"將拖動的標記對齊到其他圖層和頂點","pinningButton":"將共享頂點固定在一起"}}'),_={en:a,de:o,it:s,id:l,ro:h,ru:u,es:c,nl:p,fr:d,pt_br:JSON.parse('{"tooltips":{"placeMarker":"Clique para posicionar o marcador","firstVertex":"Clique para posicionar o primeiro vértice","continueLine":"Clique para continuar desenhando","finishLine":"Clique em qualquer marcador existente para finalizar","finishPoly":"Clique no primeiro ponto para fechar o polígono","finishRect":"Clique para finalizar","startCircle":"Clique para posicionar o centro do círculo","finishCircle":"Clique para fechar o círculo","placeCircleMarker":"Clique para posicionar o marcador circular"},"actions":{"finish":"Finalizar","cancel":"Cancelar","removeLastVertex":"Remover último vértice"},"buttonTitles":{"drawMarkerButton":"Desenhar um marcador","drawPolyButton":"Desenhar um polígono","drawLineButton":"Desenhar uma polilinha","drawCircleButton":"Desenhar um círculo","drawRectButton":"Desenhar um retângulo","editButton":"Editar camada(s)","dragButton":"Mover camada(s)","cutButton":"Recortar camada(s)","deleteButton":"Remover camada(s)","drawCircleMarkerButton":"Marcador de círculos de desenho","snappingButton":"Marcador arrastado para outras camadas e vértices","pinningButton":"Vértices compartilhados de pinos juntos"}}'),zh:f,zh_tw:g,pl:JSON.parse('{"tooltips":{"placeMarker":"Kliknij, aby ustawić znacznik","firstVertex":"Kliknij, aby ustawić pierwszy punkt","continueLine":"Kliknij, aby kontynuować rysowanie","finishLine":"Kliknij dowolny punkt, aby zakończyć","finishPoly":"Kliknij pierwszy punkt, aby zakończyć","finishRect":"Kliknij, aby zakończyć","startCircle":"Kliknij, aby ustawić środek koła","finishCircle":"Kliknij, aby zakończyć rysowanie koła","placeCircleMarker":"Kliknij, aby ustawić okrągły znacznik"},"actions":{"finish":"Zakończ","cancel":"Anuluj","removeLastVertex":"Usuń ostatni punkt"},"buttonTitles":{"drawMarkerButton":"Narysuj znacznik","drawPolyButton":"Narysuj wielokąt","drawLineButton":"Narysuj ścieżkę","drawCircleButton":"Narysuj koło","drawRectButton":"Narysuj prostokąt","editButton":"Edytuj","dragButton":"Przesuń","cutButton":"Wytnij","deleteButton":"Usuń","drawCircleMarkerButton":"Narysuj okrągły znacznik","snappingButton":"Snap przeciągnięty marker na inne warstwy i wierzchołki","pinningButton":"Sworzeń wspólne wierzchołki razem"}}'),sv:JSON.parse('{"tooltips":{"placeMarker":"Klicka för att placera markör","firstVertex":"Klicka för att placera första hörnet","continueLine":"Klicka för att fortsätta rita","finishLine":"Klicka på en existerande punkt för att slutföra","finishPoly":"Klicka på den första punkten för att slutföra","finishRect":"Klicka för att slutföra","startCircle":"Klicka för att placera cirkelns centrum","finishCircle":"Klicka för att slutföra cirkeln","placeCircleMarker":"Klicka för att placera cirkelmarkör"},"actions":{"finish":"Slutför","cancel":"Avbryt","removeLastVertex":"Ta bort sista hörnet"},"buttonTitles":{"drawMarkerButton":"Rita Markör","drawPolyButton":"Rita Polygoner","drawLineButton":"Rita Linje","drawCircleButton":"Rita Cirkel","drawRectButton":"Rita Rektangel","editButton":"Redigera Lager","dragButton":"Dra Lager","cutButton":"Klipp i Lager","deleteButton":"Ta bort Lager","drawCircleMarkerButton":"Rita Cirkelmarkör","snappingButton":"Snäpp dra markören till andra lager och hörn","pinningButton":"Fäst delade hörn tillsammans"}}'),el:JSON.parse('{"tooltips":{"placeMarker":"Κάντε κλικ για να τοποθετήσετε Δείκτη","firstVertex":"Κάντε κλικ για να τοποθετήσετε το πρώτο σημείο","continueLine":"Κάντε κλικ για να συνεχίσετε να σχεδιάζετε","finishLine":"Κάντε κλικ σε οποιονδήποτε υπάρχον σημείο για να ολοκληρωθεί","finishPoly":"Κάντε κλικ στο πρώτο σημείο για να τελειώσετε","finishRect":"Κάντε κλικ για να τελειώσετε","startCircle":"Κάντε κλικ για να τοποθετήσετε κέντρο Κύκλου","finishCircle":"Κάντε κλικ για να ολοκληρώσετε τον Κύκλο","placeCircleMarker":"Κάντε κλικ για να τοποθετήσετε Κυκλικό Δείκτη"},"actions":{"finish":"Τέλος","cancel":"Ακύρωση","removeLastVertex":"Κατάργηση τελευταίου σημείου"},"buttonTitles":{"drawMarkerButton":"Σχεδίαση Δείκτη","drawPolyButton":"Σχεδίαση Πολυγώνου","drawLineButton":"Σχεδίαση Γραμμής","drawCircleButton":"Σχεδίαση Κύκλου","drawRectButton":"Σχεδίαση Ορθογωνίου","editButton":"Επεξεργασία Επιπέδων","dragButton":"Μεταφορά Επιπέδων","cutButton":"Αποκοπή Επιπέδων","deleteButton":"Κατάργηση Επιπέδων","drawCircleMarkerButton":"Σχεδίαση Κυκλικού Δείκτη","snappingButton":"Προσκόλληση του Δείκτη μεταφοράς σε άλλα Επίπεδα και Κορυφές","pinningButton":"Περικοπή κοινών κορυφών μαζί"}}'),hu:JSON.parse('{"tooltips":{"placeMarker":"Kattintson a jelölő elhelyezéséhez","firstVertex":"Kattintson az első pont elhelyezéséhez","continueLine":"Kattintson a következő pont elhelyezéséhez","finishLine":"A befejezéshez kattintson egy meglévő pontra","finishPoly":"A befejezéshez kattintson az első pontra","finishRect":"Kattintson a befejezéshez","startCircle":"Kattintson a kör középpontjának elhelyezéséhez","finishCircle":"Kattintson a kör befejezéséhez","placeCircleMarker":"Kattintson a körjelölő elhelyezéséhez"},"actions":{"finish":"Befejezés","cancel":"Mégse","removeLastVertex":"Utolsó pont eltávolítása"},"buttonTitles":{"drawMarkerButton":"Jelölő rajzolása","drawPolyButton":"Poligon rajzolása","drawLineButton":"Vonal rajzolása","drawCircleButton":"Kör rajzolása","drawRectButton":"Négyzet rajzolása","editButton":"Elemek szerkesztése","dragButton":"Elemek mozgatása","cutButton":"Elemek vágása","deleteButton":"Elemek törlése","drawCircleMarkerButton":"Kör jelölő rajzolása","snappingButton":"Kapcsolja a jelöltőt másik elemhez vagy ponthoz","pinningButton":"Közös pontok összekötése"}}'),da:JSON.parse('{"tooltips":{"placeMarker":"Tryk for at placere en markør","firstVertex":"Tryk for at placere det første punkt","continueLine":"Tryk for at fortsætte linjen","finishLine":"Tryk på et eksisterende punkt for at afslutte","finishPoly":"Tryk på det første punkt for at afslutte","finishRect":"Tryk for at afslutte","startCircle":"Tryk for at placere cirklens center","finishCircle":"Tryk for at afslutte cirklen","placeCircleMarker":"Tryk for at placere en cirkelmarkør"},"actions":{"finish":"Afslut","cancel":"Afbryd","removeLastVertex":"Fjern sidste punkt"},"buttonTitles":{"drawMarkerButton":"Placer markør","drawPolyButton":"Tegn polygon","drawLineButton":"Tegn linje","drawCircleButton":"Tegn cirkel","drawRectButton":"Tegn firkant","editButton":"Rediger","dragButton":"Træk","cutButton":"Klip","deleteButton":"Fjern","drawCircleMarkerButton":"Tegn cirkelmarkør","snappingButton":"Fastgør trukket markør til andre elementer","pinningButton":"Sammenlæg delte elementer"}}'),no:JSON.parse('{"tooltips":{"placeMarker":"Klikk for å plassere punkt","firstVertex":"Klikk for å plassere første punkt","continueLine":"Klikk for å tegne videre","finishLine":"Klikk på et eksisterende punkt for å fullføre","finishPoly":"Klikk første punkt for å fullføre","finishRect":"Klikk for å fullføre","startCircle":"Klikk for å sette sirkel midtpunkt","finishCircle":"Klikk for å fullføre sirkel","placeCircleMarker":"Klikk for å plassere sirkel"},"actions":{"finish":"Fullfør","cancel":"Kanseller","removeLastVertex":"Fjern forrige punkt"},"buttonTitles":{"drawMarkerButton":"Tegn Punkt","drawPolyButton":"Tegn Flate","drawLineButton":"Tegn Linje","drawCircleButton":"Tegn Sirkel","drawRectButton":"Tegn rektangel","editButton":"Rediger Objekter","dragButton":"Dra Objekter","cutButton":"Kutt Objekter","deleteButton":"Fjern Objekter","drawCircleMarkerButton":"Tegn sirkel-punkt","snappingButton":"Fest dratt punkt til andre objekter og punkt","pinningButton":"Pin delte punkt sammen"}}'),fa:JSON.parse('{"tooltips":{"placeMarker":"کلیک برای جانمایی نشان","firstVertex":"کلیک برای رسم اولین رأس","continueLine":"کلیک برای ادامه رسم","finishLine":"کلیک روی هر نشان موجود برای پایان","finishPoly":"کلیک روی اولین نشان برای پایان","finishRect":"کلیک برای پایان","startCircle":"کلیک برای رسم مرکز دایره","finishCircle":"کلیک برای پایان رسم دایره","placeCircleMarker":"کلیک برای رسم نشان دایره"},"actions":{"finish":"پایان","cancel":"لفو","removeLastVertex":"حذف آخرین رأس"},"buttonTitles":{"drawMarkerButton":"درج نشان","drawPolyButton":"رسم چندضلعی","drawLineButton":"رسم خط","drawCircleButton":"رسم دایره","drawRectButton":"رسم چهارضلعی","editButton":"ویرایش لایه‌ها","dragButton":"جابجایی لایه‌ها","cutButton":"برش لایه‌ها","deleteButton":"حذف لایه‌ها","drawCircleMarkerButton":"رسم نشان دایره","snappingButton":"نشانگر را به لایه‌ها و رئوس دیگر بکشید","pinningButton":"رئوس مشترک را با هم پین کنید","rotateButton":"چرخش لایه"}}'),ua:JSON.parse('{"tooltips":{"placeMarker":"Натисніть, щоб нанести маркер","firstVertex":"Натисніть, щоб нанести першу вершину","continueLine":"Натисніть, щоб продовжити малювати","finishLine":"Натисніть будь-який існуючий маркер для завершення","finishPoly":"Виберіть перший маркер, щоб завершити","finishRect":"Натисніть, щоб завершити","startCircle":"Натисніть, щоб додати центр кола","finishCircle":"Натисніть, щоб завершити коло","placeCircleMarker":"Натисніть, щоб нанести круговий маркер"},"actions":{"finish":"Завершити","cancel":"Відмінити","removeLastVertex":"Видалити попередню вершину"},"buttonTitles":{"drawMarkerButton":"Малювати маркер","drawPolyButton":"Малювати полігон","drawLineButton":"Малювати криву","drawCircleButton":"Малювати коло","drawRectButton":"Малювати прямокутник","editButton":"Редагувати шари","dragButton":"Перенести шари","cutButton":"Вирізати шари","deleteButton":"Видалити шари","drawCircleMarkerButton":"Малювати круговий маркер","snappingButton":"Прив’язати перетягнутий маркер до інших шарів та вершин","pinningButton":"Зв\'язати спільні вершини разом"}}'),tr:JSON.parse('{"tooltips":{"placeMarker":"İşaretçi yerleştirmek için tıklayın","firstVertex":"İlk tepe noktasını yerleştirmek için tıklayın","continueLine":"Çizime devam etmek için tıklayın","finishLine":"Bitirmek için mevcut herhangi bir işaretçiyi tıklayın","finishPoly":"Bitirmek için ilk işaretçiyi tıklayın","finishRect":"Bitirmek için tıklayın","startCircle":"Daire merkezine yerleştirmek için tıklayın","finishCircle":"Daireyi bitirmek için tıklayın","placeCircleMarker":"Daire işaretçisi yerleştirmek için tıklayın"},"actions":{"finish":"Bitir","cancel":"İptal","removeLastVertex":"Son köşeyi kaldır"},"buttonTitles":{"drawMarkerButton":"Çizim İşaretçisi","drawPolyButton":"Çokgenler çiz","drawLineButton":"Çoklu çizgi çiz","drawCircleButton":"Çember çiz","drawRectButton":"Dikdörtgen çiz","editButton":"Katmanları düzenle","dragButton":"Katmanları sürükle","cutButton":"Katmanları kes","deleteButton":"Katmanları kaldır","drawCircleMarkerButton":"Daire işaretçisi çiz","snappingButton":"Sürüklenen işaretçiyi diğer katmanlara ve köşelere yapıştır","pinningButton":"Paylaşılan köşeleri birbirine sabitle"}}'),cz:JSON.parse('{"tooltips":{"placeMarker":"Kliknutím vytvoříte značku","firstVertex":"Kliknutím vytvoříte první objekt","continueLine":"Kliknutím pokračujte v kreslení","finishLine":"Kliknutí na libovolnou existující značku pro dokončení","finishPoly":"Vyberte první bod pro dokončení","finishRect":"Klikněte pro dokončení","startCircle":"Kliknutím přidejte střed kruhu","finishCircle":"Нажмите, чтобы задать радиус","placeCircleMarker":"Kliknutím nastavte poloměr"},"actions":{"finish":"Dokončit","cancel":"Zrušit","removeLastVertex":"Zrušit poslední akci"},"buttonTitles":{"drawMarkerButton":"Přidat značku","drawPolyButton":"Nakreslit polygon","drawLineButton":"Nakreslit křivku","drawCircleButton":"Nakreslit kruh","drawRectButton":"Nakreslit obdélník","editButton":"Upravit vrstvu","dragButton":"Přeneste vrstvu","cutButton":"Vyjmout vrstvu","deleteButton":"Smazat vrstvu","drawCircleMarkerButton":"Přidat kruhovou značku","snappingButton":"Navázat tažnou značku k dalším vrstvám a vrcholům","pinningButton":"Spojit společné body dohromady"}}')};function m(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function y(t){for(var e=1;e0&&arguments[0]!==undefined?arguments[0]:this.globalOptions;this.globalEditModeEnabled()?this.disableGlobalEditMode():this.enableGlobalEditMode(t)},handleLayerAdditionInGlobalEditMode:function(){var t=this._addedLayers;for(var e in this._addedLayers={},t){var n=t[e];!!n.pm&&!n._pmTempLayer&&this.globalEditModeEnabled()&&n.pm.enable(y({},this.globalOptions))}},_layerAdded:function(t){var e=t.layer;this._addedLayers[L.stamp(e)]=e}};const k={_globalDragModeEnabled:!1,enableGlobalDragMode:function(){var t=L.PM.Utils.findLayers(this.map);this._globalDragModeEnabled=!0,this._addedLayersDrag={},t.forEach((function(t){t.pm.enableLayerDrag()})),this.throttledReInitDrag||(this.throttledReInitDrag=L.Util.throttle(this.reinitGlobalDragMode,100,this)),this.map.on("layeradd",this.throttledReInitDrag,this),this.map.on("layeradd",this._layerAddedDrag,this),this.Toolbar.toggleButton("dragMode",this.globalDragModeEnabled()),this._fireGlobalDragModeToggled(!0)},disableGlobalDragMode:function(){var t=L.PM.Utils.findLayers(this.map);this._globalDragModeEnabled=!1,t.forEach((function(t){t.pm.disableLayerDrag()})),this.map.off("layeradd",this.throttledReInitDrag,this),this.Toolbar.toggleButton("dragMode",this.globalDragModeEnabled()),this._fireGlobalDragModeToggled(!1)},globalDragModeEnabled:function(){return!!this._globalDragModeEnabled},toggleGlobalDragMode:function(){this.globalDragModeEnabled()?this.disableGlobalDragMode():this.enableGlobalDragMode()},reinitGlobalDragMode:function(){var t=this._addedLayersDrag;for(var e in this._addedLayersDrag={},t){var n=t[e];!!n.pm&&!n._pmTempLayer&&this.globalDragModeEnabled()&&n.pm.enableLayerDrag()}},_layerAddedDrag:function(t){var e=t.layer;this._addedLayersDrag[L.stamp(e)]=e}};const M={_globalRemovalModeEnabled:!1,enableGlobalRemovalMode:function(){var t=this;this._globalRemovalModeEnabled=!0,this.map.eachLayer((function(e){t._isRelevantForRemoval(e)&&(e.pm.disable(),e.on("click",t.removeLayer,t))})),this.throttledReInitRemoval||(this.throttledReInitRemoval=L.Util.throttle(this.reinitGlobalRemovalMode,100,this)),this.map.on("layeradd",this.throttledReInitRemoval,this),this.Toolbar.toggleButton("removalMode",this.globalRemovalModeEnabled()),this._fireGlobalRemovalModeToggled(!0)},disableGlobalRemovalMode:function(){var t=this;this._globalRemovalModeEnabled=!1,this.map.eachLayer((function(e){e.off("click",t.removeLayer,t)})),this.map.off("layeradd",this.throttledReInitRemoval,this),this.Toolbar.toggleButton("removalMode",this.globalRemovalModeEnabled()),this._fireGlobalRemovalModeToggled(!1)},globalRemovalEnabled:function(){return this.globalRemovalModeEnabled()},globalRemovalModeEnabled:function(){return!!this._globalRemovalModeEnabled},toggleGlobalRemovalMode:function(){this.globalRemovalModeEnabled()?this.disableGlobalRemovalMode():this.enableGlobalRemovalMode()},reinitGlobalRemovalMode:function(t){var e=t.layer;this._isRelevantForRemoval(e)&&this.globalRemovalModeEnabled()&&(this.disableGlobalRemovalMode(),this.enableGlobalRemovalMode())},removeLayer:function(t){var e=t.target;this._isRelevantForRemoval(e)&&!e.pm.dragging()&&(e.removeFrom(this.map.pm._getContainingLayer()),e.remove(),e instanceof L.LayerGroup?(this._fireRemoveLayerGroup(e),this._fireRemoveLayerGroup(this.map,e)):(e.pm._fireRemove(e),e.pm._fireRemove(this.map,e)))},_isRelevantForRemoval:function(t){return t.pm&&!(t instanceof L.LayerGroup)&&(!L.PM.optIn&&!t.options.pmIgnore||L.PM.optIn&&!1===t.options.pmIgnore)&&!t._pmTempLayer&&t.pm.options.allowRemoval}};const x={_globalRotateModeEnabled:!1,enableGlobalRotateMode:function(){var t=this;this._globalRotateModeEnabled=!0,L.PM.Utils.findLayers(this.map).filter((function(t){return t instanceof L.Polyline})).forEach((function(e){t._isRelevantForRotate(e)&&e.pm.enableRotate()})),this.throttledReInitRotate||(this.throttledReInitRotate=L.Util.throttle(this._reinitGlobalRotateMode,100,this)),this.map.on("layeradd",this.throttledReInitRotate,this),this.Toolbar.toggleButton("rotateMode",this.globalRotateModeEnabled()),this._fireGlobalRotateModeToggled()},disableGlobalRotateMode:function(){this._globalRotateModeEnabled=!1,L.PM.Utils.findLayers(this.map).filter((function(t){return t instanceof L.Polyline})).forEach((function(t){t.pm.disableRotate()})),this.map.off("layeradd",this.throttledReInitRotate,this),this.Toolbar.toggleButton("rotateMode",this.globalRotateModeEnabled()),this._fireGlobalRotateModeToggled()},globalRotateModeEnabled:function(){return!!this._globalRotateModeEnabled},toggleGlobalRotateMode:function(){this.globalRotateModeEnabled()?this.disableGlobalRotateMode():this.enableGlobalRotateMode()},_reinitGlobalRotateMode:function(t){var e=t.layer;this._isRelevantForRotate(e)&&this.globalRotateModeEnabled()&&(this.disableGlobalRotateMode(),this.enableGlobalRotateMode())},_isRelevantForRotate:function(t){return t.pm&&!(t instanceof L.LayerGroup)&&(!L.PM.optIn&&!t.options.pmIgnore||L.PM.optIn&&!1===t.options.pmIgnore)&&!t._pmTempLayer&&t.pm.options.allowRotation}};function w(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function C(t){for(var e=1;e0&&arguments[0]!==undefined?arguments[0]:"Draw",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this._map,"pm:drawstart",{shape:this._shape,workingLayer:this._layer},t,e)},_fireDrawEnd:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Draw",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this._map,"pm:drawend",{shape:this._shape},t,e)},_fireCreate:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"Draw",n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{};this.__fire(this._map,"pm:create",{shape:this._shape,marker:t,layer:t},e,n)},_fireCenterPlaced:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Draw",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{},n="Draw"===t?this._layer:undefined,i="Draw"!==t?this._layer:undefined;this.__fire(this._layer,"pm:centerplaced",{shape:this._shape,workingLayer:n,layer:i,latlng:this._layer.getLatLng()},t,e)},_fireCut:function(t,e,n){var i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:"Draw",r=arguments.length>4&&arguments[4]!==undefined?arguments[4]:{};this.__fire(t,"pm:cut",{shape:this._shape,layer:e,originalLayer:n},i,r)},_fireEdit:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:this._layer,e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"Edit",n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{};this.__fire(t,"pm:edit",{layer:this._layer,shape:this.getShape()},e,n)},_fireEnable:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Edit",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this._layer,"pm:enable",{layer:this._layer,shape:this.getShape()},t,e)},_fireDisable:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Edit",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this._layer,"pm:disable",{layer:this._layer,shape:this.getShape()},t,e)},_fireUpdate:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Edit",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this._layer,"pm:update",{layer:this._layer,shape:this.getShape()},t,e)},_fireMarkerDragStart:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:undefined,n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Edit",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(this._layer,"pm:markerdragstart",{layer:this._layer,markerEvent:t,shape:this.getShape(),indexPath:e},n,i)},_fireMarkerDrag:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:undefined,n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Edit",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(this._layer,"pm:markerdrag",{layer:this._layer,markerEvent:t,shape:this.getShape(),indexPath:e},n,i)},_fireMarkerDragEnd:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:undefined,n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:undefined,i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:"Edit",r=arguments.length>4&&arguments[4]!==undefined?arguments[4]:{};this.__fire(this._layer,"pm:markerdragend",{layer:this._layer,markerEvent:t,shape:this.getShape(),indexPath:e,intersectionReset:n},i,r)},_fireDragStart:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Edit",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this._layer,"pm:dragstart",{layer:this._layer,shape:this.getShape()},t,e)},_fireDrag:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"Edit",n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{};this.__fire(this._layer,"pm:drag",C(C({},t),{},{shape:this.getShape()}),e,n)},_fireDragEnd:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Edit",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this._layer,"pm:dragend",{layer:this._layer,shape:this.getShape()},t,e)},_fireRemove:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:t,n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Edit",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(t,"pm:remove",{layer:e,shape:this.getShape()},n,i)},_fireVertexAdded:function(t,e,n){var i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:"Edit",r=arguments.length>4&&arguments[4]!==undefined?arguments[4]:{};this.__fire(this._layer,"pm:vertexadded",{layer:this._layer,workingLayer:this._layer,marker:t,indexPath:e,latlng:n,shape:this.getShape()},i,r)},_fireVertexRemoved:function(t,e){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Edit",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(this._layer,"pm:vertexremoved",{layer:this._layer,marker:t,indexPath:e,shape:this.getShape()},n,i)},_fireVertexClick:function(t,e){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Edit",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(this._layer,"pm:vertexclick",{layer:this._layer,markerEvent:t,indexPath:e,shape:this.getShape()},n,i)},_fireIntersect:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"Edit",n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{};this.__fire(this._layer,"pm:intersect",{layer:this._layer,intersection:t,shape:this.getShape()},e,n)},_fireLayerReset:function(t,e){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Edit",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(this._layer,"pm:layerreset",{layer:this._layer,markerEvent:t,indexPath:e,shape:this.getShape()},n,i)},_fireSnapDrag:function(t,e){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Snapping",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(t,"pm:snapdrag",e,n,i)},_fireSnap:function(t,e){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Snapping",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(t,"pm:snap",e,n,i)},_fireUnsnap:function(t,e){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Snapping",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(t,"pm:unsnap",e,n,i)},_fireRotationEnable:function(t,e){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Rotation",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(t,"pm:rotateenable",{layer:this._layer,helpLayer:this._rotatePoly},n,i)},_fireRotationDisable:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"Rotation",n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{};this.__fire(t,"pm:rotatedisable",{layer:this._layer},e,n)},_fireRotationStart:function(t,e){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Rotation",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(t,"pm:rotatestart",{layer:this._rotationLayer,helpLayer:this._layer,startAngle:this._startAngle,originLatLngs:e},n,i)},_fireRotation:function(t,e,n){var i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:"Rotation",r=arguments.length>4&&arguments[4]!==undefined?arguments[4]:{};this.__fire(t,"pm:rotate",{layer:this._rotationLayer,helpLayer:this._layer,startAngle:this._startAngle,angle:this._rotationLayer.pm.getAngle(),angleDiff:e,oldLatLngs:n,newLatLngs:this._rotationLayer.getLatLngs()},i,r)},_fireRotationEnd:function(t,e,n){var i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:"Rotation",r=arguments.length>4&&arguments[4]!==undefined?arguments[4]:{};this.__fire(t,"pm:rotateend",{layer:this._rotationLayer,helpLayer:this._layer,startAngle:e,angle:this._rotationLayer.pm.getAngle(),originLatLngs:n,newLatLngs:this._rotationLayer.getLatLngs()},i,r)},_fireActionClick:function(t,e,n){var i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:"Toolbar",r=arguments.length>4&&arguments[4]!==undefined?arguments[4]:{};this.__fire(this._map,"pm:actionclick",{text:t.text,action:t,btnName:e,button:n},i,r)},_fireButtonClick:function(t,e){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Toolbar",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(this._map,"pm:buttonclick",{btnName:t,button:e},n,i)},_fireLangChange:function(t,e,n,i){var r=arguments.length>4&&arguments[4]!==undefined?arguments[4]:"Global",a=arguments.length>5&&arguments[5]!==undefined?arguments[5]:{};this.__fire(this.map,"pm:langchange",{oldLang:t,activeLang:e,fallback:n,translations:i},r,a)},_fireGlobalDragModeToggled:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"Global",n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{};this.__fire(this.map,"pm:globaldragmodetoggled",{enabled:t,map:this.map},e,n)},_fireGlobalEditModeToggled:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"Global",n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{};this.__fire(this.map,"pm:globaleditmodetoggled",{enabled:t,map:this.map},e,n)},_fireGlobalRemovalModeToggled:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"Global",n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{};this.__fire(this.map,"pm:globalremovalmodetoggled",{enabled:t,map:this.map},e,n)},_fireGlobalCutModeToggled:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Global",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this._map,"pm:globalcutmodetoggled",{enabled:!!this._enabled,map:this._map},t,e)},_fireGlobalDrawModeToggled:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Global",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this._map,"pm:globaldrawmodetoggled",{enabled:this._enabled,shape:this._shape,map:this._map},t,e)},_fireGlobalRotateModeToggled:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Global",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this.map,"pm:globalrotatemodetoggled",{enabled:this.globalRotateModeEnabled(),map:this.map},t,e)},_fireRemoveLayerGroup:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:t,n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Edit",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(t,"pm:remove",{layer:e,shape:undefined},n,i)},_fireKeyeventEvent:function(t,e,n){var i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:"Global",r=arguments.length>4&&arguments[4]!==undefined?arguments[4]:{};this.__fire(this.map,"pm:keyevent",{event:t,eventType:e,focusOn:n},i,r)},__fire:function(t,e,n,i){var a=arguments.length>4&&arguments[4]!==undefined?arguments[4]:{};n=r()(n,a,{source:i}),L.PM.Utils._fireEvent(t,e,n)}};const S=E;const O={_lastEvents:{keydown:undefined,keyup:undefined,current:undefined},_initKeyListener:function(t){this.map=t,L.DomEvent.on(document,"keydown keyup",this._onKeyListener,this),L.DomEvent.on(document,"blur",this._onKeyListener,this)},_onKeyListener:function(t){var e="document";this.map.getContainer().contains(t.target)&&(e="map");var n={event:t,eventType:t.type,focusOn:e};this._lastEvents[t.type]=n,this._lastEvents.current=n,this.map.pm._fireKeyeventEvent(t,t.type,e)},getLastKeyEvent:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"current";return this._lastEvents[t]},isShiftKeyPressed:function(){var t;return null===(t=this._lastEvents.current)||void 0===t?void 0:t.event.shiftKey},isAltKeyPressed:function(){var t;return null===(t=this._lastEvents.current)||void 0===t?void 0:t.event.altKey},isCtrlKeyPressed:function(){var t;return null===(t=this._lastEvents.current)||void 0===t?void 0:t.event.ctrlKey},isMetaKeyPressed:function(){var t;return null===(t=this._lastEvents.current)||void 0===t?void 0:t.event.metaKey},getPressedKey:function(){var t;return null===(t=this._lastEvents.current)||void 0===t?void 0:t.event.key}};const D=L.Class.extend({includes:[b,k,M,x,S],initialize:function(t){this.map=t,this.Draw=new L.PM.Draw(t),this.Toolbar=new L.PM.Toolbar(t),this.Keyboard=O,this.globalOptions={snappable:!0,layerGroup:undefined,snappingOrder:["Marker","CircleMarker","Circle","Line","Polygon","Rectangle"],panes:{vertexPane:"markerPane",layerPane:"overlayPane",markerPane:"markerPane"},draggable:!0},this.Keyboard._initKeyListener(t)},setLang:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"en",e=arguments.length>1?arguments[1]:undefined,n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"en",i=L.PM.activeLang;e&&(_[t]=r()(_[n],e)),L.PM.activeLang=t,this.map.pm.Toolbar.reinit(),this._fireLangChange(i,t,n,_[t])},addControls:function(t){this.Toolbar.addControls(t)},removeControls:function(){this.Toolbar.removeControls()},toggleControls:function(){this.Toolbar.toggleControls()},controlsVisible:function(){return this.Toolbar.isVisible},enableDraw:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Polygon",e=arguments.length>1?arguments[1]:undefined;"Poly"===t&&(t="Polygon"),this.Draw.enable(t,e)},disableDraw:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Polygon";"Poly"===t&&(t="Polygon"),this.Draw.disable(t)},setPathOptions:function(t){var e=this,n=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{},i=n.ignoreShapes||[],r=n.merge||!1;this.map.pm.Draw.shapes.forEach((function(n){-1===i.indexOf(n)&&e.map.pm.Draw[n].setPathOptions(t,r)}))},getGlobalOptions:function(){return this.globalOptions},setGlobalOptions:function(t){var e=this,n=r()(this.globalOptions,t),i=!1;this.map.pm.Draw.CircleMarker.enabled()&&this.map.pm.Draw.CircleMarker.options.editable!==n.editable&&(this.map.pm.Draw.CircleMarker.disable(),i=!0),this.map.pm.Draw.shapes.forEach((function(t){e.map.pm.Draw[t].setOptions(n)})),i&&this.map.pm.Draw.CircleMarker.enable(),L.PM.Utils.findLayers(this.map).forEach((function(t){t.pm.setOptions(n)})),this.applyGlobalOptions(),this.globalOptions=n},applyGlobalOptions:function(){L.PM.Utils.findLayers(this.map).forEach((function(t){t.pm.enabled()&&t.pm.applyOptions()}))},globalDrawModeEnabled:function(){return!!this.Draw.getActiveShape()},globalCutModeEnabled:function(){return!!this.Draw.Cut.enabled()},enableGlobalCutMode:function(t){return this.Draw.Cut.enable(t)},toggleGlobalCutMode:function(t){return this.Draw.Cut.toggle(t)},disableGlobalCutMode:function(){return this.Draw.Cut.disable()},getGeomanLayers:function(){var t=arguments.length>0&&arguments[0]!==undefined&&arguments[0],e=L.PM.Utils.findLayers(this.map);if(!t)return e;var n=L.featureGroup();return n._pmTempLayer=!0,e.forEach((function(t){n.addLayer(t)})),n},getGeomanDrawLayers:function(){var t=arguments.length>0&&arguments[0]!==undefined&&arguments[0],e=L.PM.Utils.findLayers(this.map).filter((function(t){return!0===t._drawnByGeoman}));if(!t)return e;var n=L.featureGroup();return n._pmTempLayer=!0,e.forEach((function(t){n.addLayer(t)})),n},_getContainingLayer:function(){return this.globalOptions.layerGroup&&this.globalOptions.layerGroup instanceof L.LayerGroup?this.globalOptions.layerGroup:this.map},_isCRSSimple:function(){return this.map.options.crs===L.CRS.Simple},_touchEventCounter:0,_addTouchEvents:function(t){0===this._touchEventCounter&&(L.DomEvent.on(t,"touchmove",this._canvasTouchMove,this),L.DomEvent.on(t,"touchstart touchend touchcancel",this._canvasTouchClick,this)),this._touchEventCounter+=1},_removeTouchEvents:function(t){1===this._touchEventCounter&&(L.DomEvent.off(t,"touchmove",this._canvasTouchMove,this),L.DomEvent.off(t,"touchstart touchend touchcancel",this._canvasTouchClick,this)),this._touchEventCounter=this._touchEventCounter<=1?0:this._touchEventCounter-1},_canvasTouchMove:function(t){this.map._renderer._onMouseMove(this._createMouseEvent("mousemove",t))},_canvasTouchClick:function(t){var e="";"touchstart"===t.type||"pointerdown"===t.type?e="mousedown":"touchend"===t.type||"pointerup"===t.type?e="mouseup":"touchcancel"!==t.type&&"pointercancel"!==t.type||(e="mouseup"),e&&this.map._renderer._onClick(this._createMouseEvent(e,t))},_createMouseEvent:function(t,e){var n,i=e.touches[0]||e.changedTouches[0];try{n=new MouseEvent(t,{bubbles:e.bubbles,cancelable:e.cancelable,view:e.view,detail:i.detail,screenX:i.screenX,screenY:i.screenY,clientX:i.clientX,clientY:i.clientY,ctrlKey:e.ctrlKey,altKey:e.altKey,shiftKey:e.shiftKey,metaKey:e.metaKey,button:e.button,relatedTarget:e.relatedTarget})}catch(r){(n=document.createEvent("MouseEvents")).initMouseEvent(t,e.bubbles,e.cancelable,e.view,i.detail,i.screenX,i.screenY,i.clientX,i.clientY,e.ctrlKey,e.altKey,e.shiftKey,e.metaKey,e.button,e.relatedTarget)}return n}});var R=n(7361),B=n.n(R),T=n(8721),I=n.n(T);function j(t){var e=L.PM.activeLang;return I()(_,e)||(e="en"),B()(_[e],t)}function G(t){return!function e(t){return t.filter((function(t){return![null,"",undefined].includes(t)})).reduce((function(t,n){return t.concat(Array.isArray(n)?e(n):n)}),[])}(t).length}function A(t){return t.reduce((function(t,e){return 0!==e.length&&t.push(Array.isArray(e)?A(e):e),t}),[])}function N(t,e,n){for(var i,r,a,o=6378137,s=6356752.3142,l=1/298.257223563,h=t.lng,u=t.lat,c=n,p=Math.PI,d=e*p/180,f=Math.sin(d),g=Math.cos(d),_=(1-l)*Math.tan(u*p/180),m=1/Math.sqrt(1+_*_),y=_*m,v=Math.atan2(_,g),b=m*f,k=1-b*b,M=k*(o*o-s*s)/(s*s),x=1+M/16384*(4096+M*(M*(320-175*M)-768)),w=M/1024*(256+M*(M*(74-47*M)-128)),C=c/(s*x),P=2*Math.PI;Math.abs(C-P)>1e-12;){i=Math.cos(2*v+C),P=C,C=c/(s*x)+w*(r=Math.sin(C))*(i+w/4*((a=Math.cos(C))*(2*i*i-1)-w/6*i*(4*r*r-3)*(4*i*i-3)))}var E=y*r-m*a*g,S=Math.atan2(y*a+m*r*g,(1-l)*Math.sqrt(b*b+E*E)),O=l/16*k*(4+l*(4-3*k)),D=h+180*(Math.atan2(r*f,m*a-y*r*g)-(1-O)*l*b*(C+O*r*(i+O*a*(2*i*i-1))))/p,R=180*S/p;return L.latLng(D,R)}function z(t,e,n,i){for(var r,a,o=!(arguments.length>4&&arguments[4]!==undefined)||arguments[4],s=[],l=0;l180?f-360:f<-180?f+360:f,L.latLng([d*r,f])}(e,r,i)}function V(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:t.getLatLngs();return t instanceof L.Polygon?L.polygon(e).getLatLngs():L.polyline(e).getLatLngs()}function F(t,e){var n,i;if(null!==(n=e.options.crs)&&void 0!==n&&null!==(i=n.projection)&&void 0!==i&&i.MAX_LATITUDE){var r,a,o=null===(r=e.options.crs)||void 0===r||null===(a=r.projection)||void 0===a?void 0:a.MAX_LATITUDE;t.lat=Math.max(Math.min(o,t.lat),-o)}return t}function K(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function H(t){for(var e=1;e-1?"pos-right":"",i=L.DomUtil.create("div","button-container ".concat(n),this._container),r=L.DomUtil.create("a","leaflet-buttons-control-button",i);r.setAttribute("role","button"),r.setAttribute("tabindex","0"),r.href="#";var a=L.DomUtil.create("div","leaflet-pm-actions-container ".concat(n),i),o=t.actions,s={cancel:{text:j("actions.cancel"),onClick:function(){this._triggerClick()}},finishMode:{text:j("actions.finish"),onClick:function(){this._triggerClick()}},removeLastVertex:{text:j("actions.removeLastVertex"),onClick:function(){this._map.pm.Draw[t.jsClass]._removeLastVertex()}},finish:{text:j("actions.finish"),onClick:function(e){this._map.pm.Draw[t.jsClass]._finishShape(e)}}};o.forEach((function(i){var r,o="string"==typeof i?i:i.name;if(s[o])r=s[o];else{if(!i.text)return;r=i}var l=L.DomUtil.create("a","leaflet-pm-action ".concat(n," action-").concat(o),a);if(l.setAttribute("role","button"),l.setAttribute("tabindex","0"),l.href="#",l.innerHTML=r.text,!t.disabled&&r.onClick){L.DomEvent.addListener(l,"click",(function(n){n.preventDefault();var i="",a=e._map.pm.Toolbar.buttons;for(var o in a)if(a[o]._button===t){i=o;break}e._fireActionClick(r,i,t)}),e),L.DomEvent.addListener(l,"click",r.onClick,e)}L.DomEvent.disableClickPropagation(l)})),t.toggleStatus&&L.DomUtil.addClass(i,"active");var l=L.DomUtil.create("div","control-icon",r);return t.title&&l.setAttribute("title",t.title),t.iconUrl&&l.setAttribute("src",t.iconUrl),t.className&&L.DomUtil.addClass(l,t.className),t.disabled||(L.DomEvent.addListener(r,"click",(function(){e._button.disableOtherButtons&&e._map.pm.Toolbar.triggerClickOnToggledButtons(e);var n="",i=e._map.pm.Toolbar.buttons;for(var r in i)if(i[r]._button===t){n=r;break}e._fireButtonClick(n,t)})),L.DomEvent.addListener(r,"click",this._triggerClick,this)),t.disabled&&(L.DomUtil.addClass(r,"pm-disabled"),L.DomUtil.addClass(l,"pm-disabled")),L.DomEvent.disableClickPropagation(r),i},_applyStyleClasses:function(){this._container&&(this._button.toggleStatus&&!1!==this._button.cssToggle?(L.DomUtil.addClass(this.buttonsDomNode,"active"),L.DomUtil.addClass(this._container,"activeChild")):(L.DomUtil.removeClass(this.buttonsDomNode,"active"),L.DomUtil.removeClass(this._container,"activeChild")))},_clicked:function(){this._button.doToggle&&this.toggle()}});function Y(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function X(t){for(var e=1;e0&&arguments[0]!==undefined?arguments[0]:this.options;"undefined"!=typeof t.editPolygon&&(t.editMode=t.editPolygon),"undefined"!=typeof t.deleteLayer&&(t.removalMode=t.deleteLayer),L.Util.setOptions(this,t),this.applyIconStyle(),this.isVisible=!0,this._showHideButtons()},applyIconStyle:function(){var t=this.getButtons(),e={geomanIcons:{drawMarker:"control-icon leaflet-pm-icon-marker",drawPolyline:"control-icon leaflet-pm-icon-polyline",drawRectangle:"control-icon leaflet-pm-icon-rectangle",drawPolygon:"control-icon leaflet-pm-icon-polygon",drawCircle:"control-icon leaflet-pm-icon-circle",drawCircleMarker:"control-icon leaflet-pm-icon-circle-marker",editMode:"control-icon leaflet-pm-icon-edit",dragMode:"control-icon leaflet-pm-icon-drag",cutPolygon:"control-icon leaflet-pm-icon-cut",removalMode:"control-icon leaflet-pm-icon-delete"}};for(var n in t){var i=t[n];L.Util.setOptions(i,{className:e.geomanIcons[n]})}},removeControls:function(){var t=this.getButtons();for(var e in t)t[e].remove();this.isVisible=!1},toggleControls:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:this.options;this.isVisible?this.removeControls():this.addControls(t)},_addButton:function(t,e){return this.buttons[t]=e,this.options[t]=this.options[t]||!1,this.buttons[t]},triggerClickOnToggledButtons:function(t){var e=["snappingOption"];for(var n in this.buttons)!e.includes(n)&&this.buttons[n]!==t&&this.buttons[n].toggled()&&this.buttons[n]._triggerClick()},toggleButton:function(t,e){var n=!(arguments.length>2&&arguments[2]!==undefined)||arguments[2];return"editPolygon"===t&&(t="editMode"),"deleteLayer"===t&&(t="removalMode"),n&&this.triggerClickOnToggledButtons(this.buttons[t]),!!this.buttons[t]&&this.buttons[t].toggle(e)},_defineButtons:function(){var t=this,e={className:"control-icon leaflet-pm-icon-marker",title:j("buttonTitles.drawMarkerButton"),jsClass:"Marker",onClick:function(){},afterClick:function(e,n){t.map.pm.Draw[n.button._button.jsClass].toggle()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,actions:["cancel"]},n={title:j("buttonTitles.drawPolyButton"),className:"control-icon leaflet-pm-icon-polygon",jsClass:"Polygon",onClick:function(){},afterClick:function(e,n){t.map.pm.Draw[n.button._button.jsClass].toggle()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,actions:["finish","removeLastVertex","cancel"]},i={className:"control-icon leaflet-pm-icon-polyline",title:j("buttonTitles.drawLineButton"),jsClass:"Line",onClick:function(){},afterClick:function(e,n){t.map.pm.Draw[n.button._button.jsClass].toggle()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,actions:["finish","removeLastVertex","cancel"]},r={title:j("buttonTitles.drawCircleButton"),className:"control-icon leaflet-pm-icon-circle",jsClass:"Circle",onClick:function(){},afterClick:function(e,n){t.map.pm.Draw[n.button._button.jsClass].toggle()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,actions:["cancel"]},a={title:j("buttonTitles.drawCircleMarkerButton"),className:"control-icon leaflet-pm-icon-circle-marker",jsClass:"CircleMarker",onClick:function(){},afterClick:function(e,n){t.map.pm.Draw[n.button._button.jsClass].toggle()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,actions:["cancel"]},o={title:j("buttonTitles.drawRectButton"),className:"control-icon leaflet-pm-icon-rectangle",jsClass:"Rectangle",onClick:function(){},afterClick:function(e,n){t.map.pm.Draw[n.button._button.jsClass].toggle()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,actions:["cancel"]},s={title:j("buttonTitles.editButton"),className:"control-icon leaflet-pm-icon-edit",onClick:function(){},afterClick:function(){t.map.pm.toggleGlobalEditMode()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,tool:"edit",actions:["finishMode"]},l={title:j("buttonTitles.dragButton"),className:"control-icon leaflet-pm-icon-drag",onClick:function(){},afterClick:function(){t.map.pm.toggleGlobalDragMode()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,tool:"edit",actions:["finishMode"]},h={title:j("buttonTitles.cutButton"),className:"control-icon leaflet-pm-icon-cut",jsClass:"Cut",onClick:function(){},afterClick:function(e,n){t.map.pm.Draw[n.button._button.jsClass].toggle({snappable:!0,cursorMarker:!0,allowSelfIntersection:!1})},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,tool:"edit",actions:["finish","removeLastVertex","cancel"]},u={title:j("buttonTitles.deleteButton"),className:"control-icon leaflet-pm-icon-delete",onClick:function(){},afterClick:function(){t.map.pm.toggleGlobalRemovalMode()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,tool:"edit",actions:["finishMode"]},c={title:j("buttonTitles.rotateButton"),className:"control-icon leaflet-pm-icon-rotate",onClick:function(){},afterClick:function(){t.map.pm.toggleGlobalRotateMode()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,tool:"edit",actions:["finishMode"]};this._addButton("drawMarker",new L.Control.PMButton(e)),this._addButton("drawPolyline",new L.Control.PMButton(i)),this._addButton("drawRectangle",new L.Control.PMButton(o)),this._addButton("drawPolygon",new L.Control.PMButton(n)),this._addButton("drawCircle",new L.Control.PMButton(r)),this._addButton("drawCircleMarker",new L.Control.PMButton(a)),this._addButton("editMode",new L.Control.PMButton(s)),this._addButton("dragMode",new L.Control.PMButton(l)),this._addButton("cutPolygon",new L.Control.PMButton(h)),this._addButton("removalMode",new L.Control.PMButton(u)),this._addButton("rotateMode",new L.Control.PMButton(c))},_showHideButtons:function(){if(this.isVisible){this.removeControls(),this.isVisible=!0;var t=this.getButtons(),e=[];for(var n in!1===this.options.drawControls&&(e=e.concat(Object.keys(t).filter((function(e){return!t[e]._button.tool})))),!1===this.options.editControls&&(e=e.concat(Object.keys(t).filter((function(e){return"edit"===t[e]._button.tool})))),!1===this.options.optionsControls&&(e=e.concat(Object.keys(t).filter((function(e){return"options"===t[e]._button.tool})))),!1===this.options.customControls&&(e=e.concat(Object.keys(t).filter((function(e){return"custom"===t[e]._button.tool})))),t)if(this.options[n]&&-1===e.indexOf(n)){var i=t[n]._button.tool;i||(i="draw"),t[n].setPosition(this._getBtnPosition(i)),t[n].addTo(this.map)}}},_getBtnPosition:function(t){return this.options.positions&&this.options.positions[t]?this.options.positions[t]:this.options.position},setBlockPosition:function(t,e){this.options.positions[t]=e,this._showHideButtons(),this.changeControlOrder()},getBlockPositions:function(){return this.options.positions},copyDrawControl:function(t,e){if(!e)throw new TypeError("Button has no name");"object"!==$(e)&&(e={name:e});var n=this._btnNameMapping(t);if(!e.name)throw new TypeError("Button has no name");if(this.buttons[e.name])throw new TypeError("Button with this name already exists");var i=this.map.pm.Draw.createNewDrawInstance(e.name,n);return e=X(X({},this.buttons[n]._button),e),{drawInstance:i,control:this.createCustomControl(e)}},createCustomControl:function(t){if(!t.name)throw new TypeError("Button has no name");if(this.buttons[t.name])throw new TypeError("Button with this name already exists");t.onClick||(t.onClick=function(){}),t.afterClick||(t.afterClick=function(){}),!1!==t.toggle&&(t.toggle=!0),t.block&&(t.block=t.block.toLowerCase()),t.block&&"draw"!==t.block||(t.block=""),t.className?-1===t.className.indexOf("control-icon")&&(t.className="control-icon ".concat(t.className)):t.className="control-icon";var e={tool:t.block,className:t.className,title:t.title||"",jsClass:t.name,onClick:t.onClick,afterClick:t.afterClick,doToggle:t.toggle,toggleStatus:!1,disableOtherButtons:!0,cssToggle:t.toggle,position:this.options.position,actions:t.actions||[],disabled:!!t.disabled};!1!==this.options[t.name]&&(this.options[t.name]=!0);var n=this._addButton(t.name,new L.Control.PMButton(e));return this.changeControlOrder(),n},changeControlOrder:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:[],e=this._shapeMapping(),n=[];t.forEach((function(t){e[t]?n.push(e[t]):n.push(t)}));var i=this.getButtons(),r={};n.forEach((function(t){i[t]&&(r[t]=i[t])}));var a=Object.keys(i).filter((function(t){return!i[t]._button.tool}));a.forEach((function(t){-1===n.indexOf(t)&&(r[t]=i[t])}));var o=Object.keys(i).filter((function(t){return"edit"===i[t]._button.tool}));o.forEach((function(t){-1===n.indexOf(t)&&(r[t]=i[t])}));var s=Object.keys(i).filter((function(t){return"options"===i[t]._button.tool}));s.forEach((function(t){-1===n.indexOf(t)&&(r[t]=i[t])}));var l=Object.keys(i).filter((function(t){return"custom"===i[t]._button.tool}));l.forEach((function(t){-1===n.indexOf(t)&&(r[t]=i[t])})),Object.keys(i).forEach((function(t){-1===n.indexOf(t)&&(r[t]=i[t])})),this.map.pm.Toolbar.buttons=r,this._showHideButtons()},getControlOrder:function(){var t=this.getButtons(),e=[];for(var n in t)e.push(n);return e},changeActionsOfControl:function(t,e){var n=this._btnNameMapping(t);if(!n)throw new TypeError("No name passed");if(!e)throw new TypeError("No actions passed");if(!this.buttons[n])throw new TypeError("Button with this name not exists");this.buttons[n]._button.actions=e,this.changeControlOrder()},setButtonDisabled:function(t,e){var n=this._btnNameMapping(t);this.buttons[n]._button.disabled=!!e,this._showHideButtons()},_shapeMapping:function(){return{Marker:"drawMarker",Circle:"drawCircle",Polygon:"drawPolygon",Rectangle:"drawRectangle",Polyline:"drawPolyline",Line:"drawPolyline",CircleMarker:"drawCircleMarker",Edit:"editMode",Drag:"dragMode",Cut:"cutPolygon",Removal:"removalMode",Rotate:"rotateMode"}},_btnNameMapping:function(t){var e=this._shapeMapping();return e[t]?e[t]:t}});function Q(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function tt(t){for(var e=1;e2&&arguments[2]!==undefined?arguments[2]:"asc";if(!e||0===Object.keys(e).length)return function(t,e){return t-e};for(var i,r=Object.keys(e),a=r.length-1,o={};a>=0;)i=r[a],o[i.toLowerCase()]=e[i],a-=1;function s(t){return t instanceof L.Marker?"Marker":t instanceof L.Circle?"Circle":t instanceof L.CircleMarker?"CircleMarker":t instanceof L.Rectangle?"Rectangle":t instanceof L.Polygon?"Polygon":t instanceof L.Polyline?"Line":undefined}return function(e,i){var r,a;if("instanceofShape"===t){if(r=s(e.layer).toLowerCase(),a=s(i.layer).toLowerCase(),!r||!a)return 0}else{if(!e.hasOwnProperty(t)||!i.hasOwnProperty(t))return 0;r=e[t].toLowerCase(),a=i[t].toLowerCase()}var l=r in o?o[r]:Number.MAX_SAFE_INTEGER,h=a in o?o[a]:Number.MAX_SAFE_INTEGER,u=0;return lh&&(u=1),"desc"===n?-1*u:u}}("instanceofShape",i)),t[0]||{}},_checkPrioritiySnapping:function(t){var e=this._map,n=t.segment[0],i=t.segment[1],r=t.latlng,a=this._getDistance(e,n,r),o=this._getDistance(e,i,r),s=a1&&arguments[1]!==undefined&&arguments[1];this.options.pathOptions=e?r()(this.options.pathOptions,t):t},getShapes:function(){return this.shapes},getShape:function(){return this._shape},enable:function(t,e){if(!t)throw new Error("Error: Please pass a shape as a parameter. Possible shapes are: ".concat(this.getShapes().join(",")));this.disable(),this[t].enable(e)},disable:function(){var t=this;this.shapes.forEach((function(e){t[e].disable()}))},addControls:function(){var t=this;this.shapes.forEach((function(e){t[e].addButton()}))},getActiveShape:function(){var t,e=this;return this.shapes.forEach((function(n){e[n]._enabled&&(t=n)})),t},_setGlobalDrawMode:function(){"Cut"===this._shape?this._fireGlobalCutModeToggled():this._fireGlobalDrawModeToggled();var t=L.PM.Utils.findLayers(this._map);this._enabled?t.forEach((function(t){L.PM.Utils.disablePopup(t)})):t.forEach((function(t){L.PM.Utils.enablePopup(t)}))},createNewDrawInstance:function(t,e){var n=this._getShapeFromBtnName(e);if(this[t])throw new TypeError("Draw Type already exists");if(!L.PM.Draw[n])throw new TypeError("There is no class L.PM.Draw.".concat(n));return this[t]=new L.PM.Draw[n](this._map),this[t].toolbarButtonName=t,this[t]._shape=t,this.shapes.push(t),this[e]&&this[t].setOptions(this[e].options),this[t].setOptions(this[t].options),this[t]},_getShapeFromBtnName:function(t){var e={drawMarker:"Marker",drawCircle:"Circle",drawPolygon:"Polygon",drawPolyline:"Line",drawRectangle:"Rectangle",drawCircleMarker:"CircleMarker",editMode:"Edit",dragMode:"Drag",cutPolygon:"Cut",removalMode:"Removal",rotateMode:"Rotate"};return e[t]?e[t]:this[t]?this[t]._shape:t},_finishLayer:function(t){t.pm&&(t.pm.setOptions(this.options),t.pm._shape=this._shape,t.pm._map=this._map),this._addDrawnLayerProp(t)},_addDrawnLayerProp:function(t){t._drawnByGeoman=!0},_setPane:function(t,e){"layerPane"===e?t.options.pane=this._map.pm.globalOptions.panes&&this._map.pm.globalOptions.panes.layerPane||"overlayPane":"vertexPane"===e?t.options.pane=this._map.pm.globalOptions.panes&&this._map.pm.globalOptions.panes.vertexPane||"markerPane":"markerPane"===e&&(t.options.pane=this._map.pm.globalOptions.panes&&this._map.pm.globalOptions.panes.markerPane||"markerPane")},_isFirstLayer:function(){return 0===(this._map||this._layer._map).pm.getGeomanLayers().length}});rt.Marker=rt.extend({initialize:function(t){this._map=t,this._shape="Marker",this.toolbarButtonName="drawMarker"},enable:function(t){var e=this;L.Util.setOptions(this,t),this._enabled=!0,this._map.on("click",this._createMarker,this),this._map.pm.Toolbar.toggleButton(this.toolbarButtonName,!0),this._hintMarker=L.marker([0,0],this.options.markerStyle),this._setPane(this._hintMarker,"markerPane"),this._hintMarker._pmTempLayer=!0,this._hintMarker.addTo(this._map),this.options.tooltips&&this._hintMarker.bindTooltip(j("tooltips.placeMarker"),{permanent:!0,offset:L.point(0,10),direction:"bottom",opacity:.8}).openTooltip(),this._layer=this._hintMarker,this._map.on("mousemove",this._syncHintMarker,this),this.options.markerEditable&&this._map.eachLayer((function(t){e.isRelevantMarker(t)&&t.pm.enable()})),this._fireDrawStart(),this._setGlobalDrawMode()},disable:function(){var t=this;this._enabled&&(this._enabled=!1,this._map.off("click",this._createMarker,this),this._hintMarker.remove(),this._map.off("mousemove",this._syncHintMarker,this),this._map.eachLayer((function(e){t.isRelevantMarker(e)&&e.pm.disable()})),this._map.pm.Toolbar.toggleButton(this.toolbarButtonName,!1),this.options.snappable&&this._cleanupSnapping(),this._fireDrawEnd(),this._setGlobalDrawMode())},enabled:function(){return this._enabled},toggle:function(t){this.enabled()?this.disable():this.enable(t)},isRelevantMarker:function(t){return t instanceof L.Marker&&t.pm&&!t._pmTempLayer},_syncHintMarker:function(t){if(this._hintMarker.setLatLng(t.latlng),this.options.snappable){var e=t;e.target=this._hintMarker,this._handleSnapping(e)}},_createMarker:function(t){if(t.latlng&&(!this.options.requireSnapToFinish||this._hintMarker._snapped||this._isFirstLayer())){this._hintMarker._snapped||this._hintMarker.setLatLng(t.latlng);var e=this._hintMarker.getLatLng(),n=new L.Marker(e,this.options.markerStyle);this._setPane(n,"markerPane"),this._finishLayer(n),n.pm||(n.options.draggable=!1),n.addTo(this._map.pm._getContainingLayer()),n.pm&&this.options.markerEditable?n.pm.enable():n.dragging&&n.dragging.disable(),this._fireCreate(n),this._cleanupSnapping(),this.options.continueDrawing||this.disable()}}});var at=6371008.8,ot={centimeters:637100880,centimetres:637100880,degrees:57.22891354143274,feet:20902260.511392,inches:39.37*at,kilometers:6371.0088,kilometres:6371.0088,meters:at,metres:at,miles:3958.761333810546,millimeters:6371008800,millimetres:6371008800,nauticalmiles:at/1852,radians:1,yards:6967335.223679999};function st(t,e,n){void 0===n&&(n={});var i={type:"Feature"};return(0===n.id||n.id)&&(i.id=n.id),n.bbox&&(i.bbox=n.bbox),i.properties=e||{},i.geometry=t,i}function lt(t,e,n){if(void 0===n&&(n={}),!t)throw new Error("coordinates is required");if(!Array.isArray(t))throw new Error("coordinates must be an Array");if(t.length<2)throw new Error("coordinates must be at least 2 numbers long");if(!gt(t[0])||!gt(t[1]))throw new Error("coordinates must contain numbers");return st({type:"Point",coordinates:t},e,n)}function ht(t,e,n){if(void 0===n&&(n={}),t.length<2)throw new Error("coordinates must be an array of two or more positions");return st({type:"LineString",coordinates:t},e,n)}function ut(t,e){void 0===e&&(e={});var n={type:"FeatureCollection"};return e.id&&(n.id=e.id),e.bbox&&(n.bbox=e.bbox),n.features=t,n}function ct(t,e){void 0===e&&(e="kilometers");var n=ot[e];if(!n)throw new Error(e+" units is invalid");return t*n}function pt(t,e){void 0===e&&(e="kilometers");var n=ot[e];if(!n)throw new Error(e+" units is invalid");return t/n}function dt(t){return 180*(t%(2*Math.PI))/Math.PI}function ft(t){return t%360*Math.PI/180}function gt(t){return!isNaN(t)&&null!==t&&!Array.isArray(t)}function _t(t){var e,n,i={type:"FeatureCollection",features:[]};if("LineString"===(n="Feature"===t.type?t.geometry:t).type)e=[n.coordinates];else if("MultiLineString"===n.type)e=n.coordinates;else if("MultiPolygon"===n.type)e=[].concat.apply([],n.coordinates);else{if("Polygon"!==n.type)throw new Error("Input must be a LineString, MultiLineString, Polygon, or MultiPolygon Feature or Geometry");e=n.coordinates}return e.forEach((function(t){e.forEach((function(e){for(var n=0;n=0&&h<=1&&(p.onLine1=!0),u>=0&&u<=1&&(p.onLine2=!0),!(!p.onLine1||!p.onLine2)&&[p.x,p.y])}function yt(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function vt(t){for(var e=1;e=2&&!Array.isArray(t[0])&&!Array.isArray(t[1]))return t;throw new Error("coord must be GeoJSON Point or an Array of numbers")}function wt(t){if(Array.isArray(t))return t;if("Feature"===t.type){if(null!==t.geometry)return t.geometry.coordinates}else if(t.coordinates)return t.coordinates;throw new Error("coords must be GeoJSON Feature, Geometry Object or an Array")}function Ct(t){return"Feature"===t.type?t.geometry:t}function Pt(t,e){return"FeatureCollection"===t.type?"FeatureCollection":"GeometryCollection"===t.type?"GeometryCollection":"Feature"===t.type&&null!==t.geometry?t.geometry.type:t.type}function Et(t,e,n){if(null!==t)for(var i,r,a,o,s,l,h,u,c=0,p=0,d=t.type,f="FeatureCollection"===d,g="Feature"===d,_=f?t.features.length:1,m=0;m<_;m++){s=(u=!!(h=f?t.features[m].geometry:g?t.geometry:t)&&"GeometryCollection"===h.type)?h.geometries.length:1;for(var y=0;y0){var e=t[t.length-1];this._hintline.setLatLngs([e,this._hintMarker.getLatLng()])}},_syncHintMarker:function(t){if(this._hintMarker.setLatLng(t.latlng),this.options.snappable){var e=t;e.target=this._hintMarker,this._handleSnapping(e)}this.options.allowSelfIntersection||this._handleSelfIntersection(!0,t.latlng)},hasSelfIntersection:function(){return _t(this._layer.toGeoJSON(15)).features.length>0},_handleSelfIntersection:function(t,e){var n=L.polyline(this._layer.getLatLngs());t&&(e||(e=this._hintMarker.getLatLng()),n.addLatLng(e));var i=_t(n.toGeoJSON(15));this._doesSelfIntersect=i.features.length>0,this._doesSelfIntersect?this._hintline.setStyle({color:"#f00000ff"}):this._hintline.isEmpty()||this._hintline.setStyle(this.options.hintlineStyle)},_createVertex:function(t){if(this.options.allowSelfIntersection||(this._handleSelfIntersection(!0,t.latlng),!this._doesSelfIntersect)){this._hintMarker._snapped||this._hintMarker.setLatLng(t.latlng);var e=this._hintMarker.getLatLng();if(e.equals(this._layer.getLatLngs()[0]))this._finishShape(t);else{this._layer._latlngInfo=this._layer._latlngInfo||[],this._layer._latlngInfo.push({latlng:e,snapInfo:this._hintMarker._snapInfo}),this._layer.addLatLng(e);var n=this._createMarker(e);this._setTooltipText(),this._hintline.setLatLngs([e,e]),this._fireVertexAdded(n,undefined,e,"Draw"),"snap"===this.options.finishOn&&this._hintMarker._snapped&&this._finishShape(t)}}},_removeLastVertex:function(){var t=this._layer.getLatLngs(),e=t.pop();if(t.length<1)this.disable();else{var n=this._layerGroup.getLayers().filter((function(t){return t instanceof L.Marker})).filter((function(t){return!L.DomUtil.hasClass(t._icon,"cursor-marker")})).find((function(t){return t.getLatLng()===e})),i=this._layerGroup.getLayers().filter((function(t){return t instanceof L.Marker})),r=L.PM.Utils.findDeepMarkerIndex(i,n).indexPath;this._layerGroup.removeLayer(n),this._layer.setLatLngs(t),this._syncHintLine(),this._setTooltipText(),this._fireVertexRemoved(n,r,"Draw")}},_finishShape:function(){if((this.options.allowSelfIntersection||(this._handleSelfIntersection(!1),!this._doesSelfIntersect))&&(!this.options.requireSnapToFinish||this._hintMarker._snapped||this._isFirstLayer())){var t=this._layer.getLatLngs();if(!(t.length<=1)){var e=L.polyline(t,this.options.pathOptions);this._setPane(e,"layerPane"),this._finishLayer(e),e.addTo(this._map.pm._getContainingLayer()),this._fireCreate(e),this.options.snappable&&this._cleanupSnapping(),this.disable(),this.options.continueDrawing&&this.enable()}}},_createMarker:function(t){var e=new L.Marker(t,{draggable:!1,icon:L.divIcon({className:"marker-icon"})});return this._setPane(e,"vertexPane"),e._pmTempLayer=!0,this._layerGroup.addLayer(e),e.on("click",this._finishShape,this),e},_setTooltipText:function(){var t="";t=j(this._layer.getLatLngs().flat().length<=1?"tooltips.continueLine":"tooltips.finishLine"),this._hintMarker.setTooltipContent(t)}}),rt.Polygon=rt.Line.extend({initialize:function(t){this._map=t,this._shape="Polygon",this.toolbarButtonName="drawPolygon"},_createMarker:function(t){var e=new L.Marker(t,{draggable:!1,icon:L.divIcon({className:"marker-icon"})});return this._setPane(e,"vertexPane"),e._pmTempLayer=!0,this._layerGroup.addLayer(e),1===this._layer.getLatLngs().flat().length?(e.on("click",this._finishShape,this),this._tempSnapLayerIndex=this._otherSnapLayers.push(e)-1,this.options.snappable&&this._cleanupSnapping()):e.on("click",(function(){return 1})),e},_setTooltipText:function(){var t="";t=j(this._layer.getLatLngs().flat().length<=2?"tooltips.continueLine":"tooltips.finishPoly"),this._hintMarker.setTooltipContent(t)},_finishShape:function(){if((this.options.allowSelfIntersection||(this._handleSelfIntersection(!0,this._layer.getLatLngs()[0]),!this._doesSelfIntersect))&&(!this.options.requireSnapToFinish||this._hintMarker._snapped||this._isFirstLayer())){var t=this._layer.getLatLngs();if(!(t.length<=2)){var e=L.polygon(t,this.options.pathOptions);this._setPane(e,"layerPane"),this._finishLayer(e),e.addTo(this._map.pm._getContainingLayer()),this._fireCreate(e),this._cleanupSnapping(),this._otherSnapLayers.splice(this._tempSnapLayerIndex,1),delete this._tempSnapLayerIndex,this.disable(),this.options.continueDrawing&&this.enable()}}}}),rt.Rectangle=rt.extend({initialize:function(t){this._map=t,this._shape="Rectangle",this.toolbarButtonName="drawRectangle"},enable:function(t){if(L.Util.setOptions(this,t),this._enabled=!0,this._layerGroup=new L.LayerGroup,this._layerGroup._pmTempLayer=!0,this._layerGroup.addTo(this._map),this._layer=L.rectangle([[0,0],[0,0]],this.options.pathOptions),this._setPane(this._layer,"layerPane"),this._layer._pmTempLayer=!0,this._startMarker=L.marker([0,0],{icon:L.divIcon({className:"marker-icon rect-start-marker"}),draggable:!1,zIndexOffset:-100,opacity:this.options.cursorMarker?1:0}),this._setPane(this._startMarker,"vertexPane"),this._startMarker._pmTempLayer=!0,this._layerGroup.addLayer(this._startMarker),this._hintMarker=L.marker([0,0],{zIndexOffset:150,icon:L.divIcon({className:"marker-icon cursor-marker"})}),this._setPane(this._hintMarker,"vertexPane"),this._hintMarker._pmTempLayer=!0,this._layerGroup.addLayer(this._hintMarker),this.options.tooltips&&this._hintMarker.bindTooltip(j("tooltips.firstVertex"),{permanent:!0,offset:L.point(0,10),direction:"bottom",opacity:.8}).openTooltip(),this.options.cursorMarker){L.DomUtil.addClass(this._hintMarker._icon,"visible"),this._styleMarkers=[];for(var e=0;e<2;e+=1){var n=L.marker([0,0],{icon:L.divIcon({className:"marker-icon rect-style-marker"}),draggable:!1,zIndexOffset:100});this._setPane(n,"vertexPane"),n._pmTempLayer=!0,this._layerGroup.addLayer(n),this._styleMarkers.push(n)}}this._map._container.style.cursor="crosshair",this._map.on("click",this._placeStartingMarkers,this),this._map.on("mousemove",this._syncHintMarker,this),this._map.pm.Toolbar.toggleButton(this.toolbarButtonName,!0),this._otherSnapLayers=[],this._fireDrawStart(),this._setGlobalDrawMode()},disable:function(){this._enabled&&(this._enabled=!1,this._map._container.style.cursor="",this._map.off("click",this._finishShape,this),this._map.off("click",this._placeStartingMarkers,this),this._map.off("mousemove",this._syncHintMarker,this),this._map.removeLayer(this._layerGroup),this._map.pm.Toolbar.toggleButton(this.toolbarButtonName,!1),this.options.snappable&&this._cleanupSnapping(),this._fireDrawEnd(),this._setGlobalDrawMode())},enabled:function(){return this._enabled},toggle:function(t){this.enabled()?this.disable():this.enable(t)},_placeStartingMarkers:function(t){this._hintMarker._snapped||this._hintMarker.setLatLng(t.latlng);var e=this._hintMarker.getLatLng();L.DomUtil.addClass(this._startMarker._icon,"visible"),this._startMarker.setLatLng(e),this.options.cursorMarker&&this._styleMarkers&&this._styleMarkers.forEach((function(t){L.DomUtil.addClass(t._icon,"visible"),t.setLatLng(e)})),this._map.off("click",this._placeStartingMarkers,this),this._map.on("click",this._finishShape,this),this._hintMarker.setTooltipContent(j("tooltips.finishRect")),this._setRectangleOrigin()},_setRectangleOrigin:function(){var t=this._startMarker.getLatLng();t&&(this._layerGroup.addLayer(this._layer),this._layer.setLatLngs([t,t]),this._hintMarker.on("move",this._syncRectangleSize,this))},_syncHintMarker:function(t){if(this._hintMarker.setLatLng(t.latlng),this.options.snappable){var e=t;e.target=this._hintMarker,this._handleSnapping(e)}},_syncRectangleSize:function(){var t=this,e=F(this._startMarker.getLatLng(),this._map),n=F(this._hintMarker.getLatLng(),this._map),i=L.PM.Utils._getRotatedRectangle(e,n,this.options.rectangleAngle||0,this._map);if(this._layer.setLatLngs(i),this.options.cursorMarker&&this._styleMarkers){var r=[];i.forEach((function(t){t.equals(e,1e-8)||t.equals(n,1e-8)||r.push(t)})),r.forEach((function(e,n){try{t._styleMarkers[n].setLatLng(e)}catch(i){}}))}},_findCorners:function(){var t=this._layer.getBounds();return[t.getNorthWest(),t.getNorthEast(),t.getSouthEast(),t.getSouthWest()]},_finishShape:function(t){this._hintMarker._snapped||this._hintMarker.setLatLng(t.latlng);var e=this._hintMarker.getLatLng(),n=this._startMarker.getLatLng();if(!this.options.requireSnapToFinish||this._hintMarker._snapped||this._isFirstLayer()){var i=L.rectangle([n,e],this.options.pathOptions);if(this.options.rectangleAngle){var r=L.PM.Utils._getRotatedRectangle(n,e,this.options.rectangleAngle||0,this._map);i.setLatLngs(r),i.pm&&i.pm._setAngle(this.options.rectangleAngle||0)}this._setPane(i,"layerPane"),this._finishLayer(i),i.addTo(this._map.pm._getContainingLayer()),this._fireCreate(i),this.disable(),this.options.continueDrawing&&this.enable()}}}),rt.Circle=rt.extend({initialize:function(t){this._map=t,this._shape="Circle",this.toolbarButtonName="drawCircle"},enable:function(t){L.Util.setOptions(this,t),this.options.radius=0,this._enabled=!0,this._layerGroup=new L.LayerGroup,this._layerGroup._pmTempLayer=!0,this._layerGroup.addTo(this._map),this._layer=L.circle([0,0],vt(vt({},this.options.templineStyle),{},{radius:0})),this._setPane(this._layer,"layerPane"),this._layer._pmTempLayer=!0,this._centerMarker=L.marker([0,0],{icon:L.divIcon({className:"marker-icon"}),draggable:!1,zIndexOffset:100}),this._setPane(this._centerMarker,"vertexPane"),this._centerMarker._pmTempLayer=!0,this._hintMarker=L.marker([0,0],{zIndexOffset:110,icon:L.divIcon({className:"marker-icon cursor-marker"})}),this._setPane(this._hintMarker,"vertexPane"),this._hintMarker._pmTempLayer=!0,this._layerGroup.addLayer(this._hintMarker),this.options.cursorMarker&&L.DomUtil.addClass(this._hintMarker._icon,"visible"),this.options.tooltips&&this._hintMarker.bindTooltip(j("tooltips.startCircle"),{permanent:!0,offset:L.point(0,10),direction:"bottom",opacity:.8}).openTooltip(),this._hintline=L.polyline([],this.options.hintlineStyle),this._setPane(this._hintline,"layerPane"),this._hintline._pmTempLayer=!0,this._layerGroup.addLayer(this._hintline),this._map._container.style.cursor="crosshair",this._map.on("click",this._placeCenterMarker,this),this._map.on("mousemove",this._syncHintMarker,this),this._map.pm.Toolbar.toggleButton(this.toolbarButtonName,!0),this._otherSnapLayers=[],this._fireDrawStart(),this._setGlobalDrawMode()},disable:function(){this._enabled&&(this._enabled=!1,this._map._container.style.cursor="",this._map.off("click",this._finishShape,this),this._map.off("click",this._placeCenterMarker,this),this._map.off("mousemove",this._syncHintMarker,this),this._map.removeLayer(this._layerGroup),this._map.pm.Toolbar.toggleButton(this.toolbarButtonName,!1),this.options.snappable&&this._cleanupSnapping(),this._fireDrawEnd(),this._setGlobalDrawMode())},enabled:function(){return this._enabled},toggle:function(t){this.enabled()?this.disable():this.enable(t)},_syncHintLine:function(){var t=this._centerMarker.getLatLng(),e=this._getNewDestinationOfHintMarker();this._hintline.setLatLngs([t,e])},_syncCircleRadius:function(){var t,e=this._centerMarker.getLatLng(),n=this._hintMarker.getLatLng();t=this._map.options.crs===L.CRS.Simple?this._map.distance(e,n):e.distanceTo(n),this.options.minRadiusCircle&&tthis.options.maxRadiusCircle?this._layer.setRadius(this.options.maxRadiusCircle):this._layer.setRadius(t)},_syncHintMarker:function(t){if(this._hintMarker.setLatLng(t.latlng),this._hintMarker.setLatLng(this._getNewDestinationOfHintMarker()),this.options.snappable){var e=t;e.target=this._hintMarker,this._handleSnapping(e)}this._handleHintMarkerSnapping()},_placeCenterMarker:function(t){this._layerGroup.addLayer(this._layer),this._layerGroup.addLayer(this._centerMarker),this._hintMarker._snapped||this._hintMarker.setLatLng(t.latlng);var e=this._hintMarker.getLatLng();this._layerGroup.addLayer(this._layer),this._centerMarker.setLatLng(e),this._map.off("click",this._placeCenterMarker,this),this._map.on("click",this._finishShape,this),this._placeCircleCenter()},_placeCircleCenter:function(){var t=this._centerMarker.getLatLng();t&&(this._layer.setLatLng(t),this._hintMarker.on("move",this._syncHintLine,this),this._hintMarker.on("move",this._syncCircleRadius,this),this._hintMarker.setTooltipContent(j("tooltips.finishCircle")),this._fireCenterPlaced())},_finishShape:function(t){if(!this.options.requireSnapToFinish||this._hintMarker._snapped||this._isFirstLayer()){this._hintMarker._snapped||this._hintMarker.setLatLng(t.latlng);var e,n=this._centerMarker.getLatLng(),i=this._hintMarker.getLatLng();e=this._map.options.crs===L.CRS.Simple?this._map.distance(n,i):n.distanceTo(i),this.options.minRadiusCircle&&ethis.options.maxRadiusCircle&&(e=this.options.maxRadiusCircle);var r=vt(vt({},this.options.pathOptions),{},{radius:e}),a=L.circle(n,r);this._setPane(a,"layerPane"),this._finishLayer(a),a.addTo(this._map.pm._getContainingLayer()),a.pm&&a.pm._updateHiddenPolyCircle(),this._fireCreate(a),this.disable(),this.options.continueDrawing&&this.enable()}},_getNewDestinationOfHintMarker:function(){var t=this._centerMarker.getLatLng(),e=this._hintMarker.getLatLng(),n=t.distanceTo(e);return t.equals(L.latLng([0,0]))||(this.options.minRadiusCircle&&nthis.options.maxRadiusCircle&&(e=U(this._map,t,e,this.options.maxRadiusCircle))),e},_handleHintMarkerSnapping:function(){if(this._hintMarker._snapped){var t=this._centerMarker.getLatLng(),e=this._hintMarker.getLatLng(),n=t.distanceTo(e);t.equals(L.latLng([0,0]))||(this.options.minRadiusCircle&&nthis.options.maxRadiusCircle)&&this._hintMarker.setLatLng(this._hintMarker._orgLatLng)}this._hintMarker.setLatLng(this._getNewDestinationOfHintMarker())}}),rt.CircleMarker=rt.Marker.extend({initialize:function(t){this._map=t,this._shape="CircleMarker",this.toolbarButtonName="drawCircleMarker",this._layerIsDragging=!1},enable:function(t){var e=this;L.Util.setOptions(this,t),this._enabled=!0,this._map.pm.Toolbar.toggleButton(this.toolbarButtonName,!0),this.options.editable?(this._layerGroup=new L.LayerGroup,this._layerGroup._pmTempLayer=!0,this._layerGroup.addTo(this._map),this._layer=L.circleMarker([0,0],this.options.templineStyle),this._setPane(this._layer,"layerPane"),this._layer._pmTempLayer=!0,this._centerMarker=L.marker([0,0],{icon:L.divIcon({className:"marker-icon"}),draggable:!1,zIndexOffset:100}),this._setPane(this._centerMarker,"vertexPane"),this._centerMarker._pmTempLayer=!0,this._hintMarker=L.marker([0,0],{zIndexOffset:110,icon:L.divIcon({className:"marker-icon cursor-marker"})}),this._setPane(this._hintMarker,"vertexPane"),this._hintMarker._pmTempLayer=!0,this._layerGroup.addLayer(this._hintMarker),this.options.cursorMarker&&L.DomUtil.addClass(this._hintMarker._icon,"visible"),this.options.tooltips&&this._hintMarker.bindTooltip(j("tooltips.startCircle"),{permanent:!0,offset:L.point(0,10),direction:"bottom",opacity:.8}).openTooltip(),this._hintline=L.polyline([],this.options.hintlineStyle),this._setPane(this._hintline,"layerPane"),this._hintline._pmTempLayer=!0,this._layerGroup.addLayer(this._hintline),this._map.on("click",this._placeCenterMarker,this),this._map._container.style.cursor="crosshair"):(this._map.on("click",this._createMarker,this),this._hintMarker=L.circleMarker([0,0],this.options.templineStyle),this._setPane(this._hintMarker,"layerPane"),this._hintMarker._pmTempLayer=!0,this._hintMarker.addTo(this._map),this._layer=this._hintMarker,this.options.tooltips&&this._hintMarker.bindTooltip(j("tooltips.placeCircleMarker"),{permanent:!0,offset:L.point(0,10),direction:"bottom",opacity:.8}).openTooltip()),this._map.on("mousemove",this._syncHintMarker,this),!this.options.editable&&this.options.markerEditable&&this._map.eachLayer((function(t){e.isRelevantMarker(t)&&t.pm.enable()})),this._layer.bringToBack(),this._fireDrawStart(),this._setGlobalDrawMode()},disable:function(){var t=this;this._enabled&&(this._enabled=!1,this.options.editable?(this._map._container.style.cursor="",this._map.off("click",this._finishShape,this),this._map.off("click",this._placeCenterMarker,this),this._map.removeLayer(this._layerGroup)):(this._map.off("click",this._createMarker,this),this._map.eachLayer((function(e){t.isRelevantMarker(e)&&e.pm.disable()})),this._hintMarker.remove()),this._map.off("mousemove",this._syncHintMarker,this),this._map.pm.Toolbar.toggleButton(this.toolbarButtonName,!1),this.options.snappable&&this._cleanupSnapping(),this._fireDrawEnd(),this._setGlobalDrawMode())},_placeCenterMarker:function(t){this._layerGroup.addLayer(this._layer),this._layerGroup.addLayer(this._centerMarker),this._hintMarker._snapped||this._hintMarker.setLatLng(t.latlng);var e=this._hintMarker.getLatLng();this._layerGroup.addLayer(this._layer),this._centerMarker.setLatLng(e),this._map.off("click",this._placeCenterMarker,this),this._map.on("click",this._finishShape,this),this._placeCircleCenter()},_placeCircleCenter:function(){var t=this._centerMarker.getLatLng();t&&(this._layer.setLatLng(t),this._hintMarker.on("move",this._syncHintLine,this),this._hintMarker.on("move",this._syncCircleRadius,this),this._hintMarker.setTooltipContent(j("tooltips.finishCircle")),this._fireCenterPlaced())},_syncHintLine:function(){var t=this._centerMarker.getLatLng(),e=this._getNewDestinationOfHintMarker();this._hintline.setLatLngs([t,e])},_syncCircleRadius:function(){var t=this._centerMarker.getLatLng(),e=this._hintMarker.getLatLng(),n=this._map.project(t).distanceTo(this._map.project(e));this.options.minRadiusCircleMarker&&nthis.options.maxRadiusCircleMarker?this._layer.setRadius(this.options.maxRadiusCircleMarker):this._layer.setRadius(n)},_syncHintMarker:function(t){if(this._hintMarker.setLatLng(t.latlng),this._hintMarker.setLatLng(this._getNewDestinationOfHintMarker()),this.options.snappable){var e=t;e.target=this._hintMarker,this._handleSnapping(e)}this._handleHintMarkerSnapping()},isRelevantMarker:function(t){return t instanceof L.CircleMarker&&!(t instanceof L.Circle)&&t.pm&&!t._pmTempLayer},_createMarker:function(t){if((!this.options.requireSnapToFinish||this._hintMarker._snapped||this._isFirstLayer())&&t.latlng&&!this._layerIsDragging){this._hintMarker._snapped||this._hintMarker.setLatLng(t.latlng);var e=this._hintMarker.getLatLng(),n=L.circleMarker(e,this.options.pathOptions);this._setPane(n,"layerPane"),this._finishLayer(n),n.addTo(this._map.pm._getContainingLayer()),n.pm&&this.options.markerEditable&&n.pm.enable(),this._fireCreate(n),this._cleanupSnapping(),this.options.continueDrawing||this.disable()}},_finishShape:function(t){if(!this.options.requireSnapToFinish||this._hintMarker._snapped||this._isFirstLayer()){this._hintMarker._snapped||this._hintMarker.setLatLng(t.latlng);var e=this._centerMarker.getLatLng(),n=this._hintMarker.getLatLng(),i=this._map.project(e).distanceTo(this._map.project(n));this.options.editable&&(this.options.minRadiusCircleMarker&&ithis.options.maxRadiusCircleMarker&&(i=this.options.maxRadiusCircleMarker));var r=kt(kt({},this.options.pathOptions),{},{radius:i}),a=L.circleMarker(e,r);this._setPane(a,"layerPane"),this._finishLayer(a),a.addTo(this._map.pm._getContainingLayer()),a.pm&&a.pm._updateHiddenPolyCircle(),this._fireCreate(a),this.disable(),this.options.continueDrawing&&this.enable()}},_getNewDestinationOfHintMarker:function(){var t=this._hintMarker.getLatLng();if(this.options.editable){var e=this._centerMarker.getLatLng();if(e.equals(L.latLng([0,0])))return t;var n=this._map.project(e).distanceTo(this._map.project(t));this.options.minRadiusCircleMarker&&nthis.options.maxRadiusCircleMarker&&(t=U(this._map,e,t,this._pxRadiusToMeter(this.options.maxRadiusCircleMarker)))}return t},_handleHintMarkerSnapping:function(){if(this.options.editable){if(this._hintMarker._snapped){var t=this._centerMarker.getLatLng(),e=this._hintMarker.getLatLng(),n=this._map.project(t).distanceTo(this._map.project(e));(this.options.minRadiusCircleMarker&&nthis.options.maxRadiusCircleMarker)&&this._hintMarker.setLatLng(this._hintMarker._orgLatLng)}this._hintMarker.setLatLng(this._getNewDestinationOfHintMarker())}},_pxRadiusToMeter:function(t){var e=this._centerMarker.getLatLng(),n=this._map.project(e),i=L.point(n.x+t,n.y);return this._map.unproject(i).distanceTo(e)}});const Rt=function(t){if(!t)throw new Error("geojson is required");var e=[];return Dt(t,(function(t){!function(t,e){var n=[],i=t.geometry;if(null!==i){switch(i.type){case"Polygon":n=wt(i);break;case"LineString":n=[wt(i)]}n.forEach((function(n){var i=function(t,e){var n=[];return t.reduce((function(t,i){var r,a,o,s,l,h,u=ht([t,i],e);return u.bbox=(a=i,o=(r=t)[0],s=r[1],l=a[0],h=a[1],[ol?o:l,s>h?s:h]),n.push(u),i})),n}(n,t.properties);i.forEach((function(t){t.id=e.length,e.push(t)}))}))}}(t,e)})),ut(e)};var Bt=n(1787);function Tt(t,e){var n=wt(t),i=wt(e);if(2!==n.length)throw new Error(" line1 must only contain 2 coordinates");if(2!==i.length)throw new Error(" line2 must only contain 2 coordinates");var r=n[0][0],a=n[0][1],o=n[1][0],s=n[1][1],l=i[0][0],h=i[0][1],u=i[1][0],c=i[1][1],p=(c-h)*(o-r)-(u-l)*(s-a),d=(u-l)*(a-h)-(c-h)*(r-l),f=(o-r)*(a-h)-(s-a)*(r-l);if(0===p)return null;var g=d/p,_=f/p;return g>=0&&g<=1&&_>=0&&_<=1?lt([r+g*(o-r),a+g*(s-a)]):null}const It=function(t,e){var n={},i=[];if("LineString"===t.type&&(t=st(t)),"LineString"===e.type&&(e=st(e)),"Feature"===t.type&&"Feature"===e.type&&null!==t.geometry&&null!==e.geometry&&"LineString"===t.geometry.type&&"LineString"===e.geometry.type&&2===t.geometry.coordinates.length&&2===e.geometry.coordinates.length){var r=Tt(t,e);return r&&i.push(r),ut(i)}var a=Bt();return a.load(Rt(e)),St(Rt(t),(function(t){St(a.search(t),(function(e){var r=Tt(t,e);if(r){var a=wt(r).join(",");n[a]||(n[a]=!0,i.push(r))}}))})),ut(i)};const jt=function(t,e,n){void 0===n&&(n={});var i=xt(t),r=xt(e),a=ft(r[1]-i[1]),o=ft(r[0]-i[0]),s=ft(i[1]),l=ft(r[1]),h=Math.pow(Math.sin(a/2),2)+Math.pow(Math.sin(o/2),2)*Math.cos(s)*Math.cos(l);return ct(2*Math.atan2(Math.sqrt(h),Math.sqrt(1-h)),n.units)};const Gt=function(t){var e=t[0],n=t[1],i=t[2],r=t[3];if(jt(t.slice(0,2),[i,n])>=jt(t.slice(0,2),[e,r])){var a=(n+r)/2;return[e,a-(i-e)/2,i,a+(i-e)/2]}var o=(e+i)/2;return[o-(r-n)/2,n,o+(r-n)/2,r]};function At(t){var e=[Infinity,Infinity,-Infinity,-Infinity];return Et(t,(function(t){e[0]>t[0]&&(e[0]=t[0]),e[1]>t[1]&&(e[1]=t[1]),e[2] is required");if("number"!=typeof n)throw new Error(" must be a number");if("number"!=typeof i)throw new Error(" must be a number");!1!==r&&r!==undefined||(t=JSON.parse(JSON.stringify(t)));var a=Math.pow(10,n);return Et(t,(function(t){!function(t,e,n){t.length>n&&t.splice(n,t.length);for(var i=0;i0&&((g=f.features[0]).properties.dist=jt(e,g,n),g.properties.location=r+jt(s,g,n)),s.properties.dist1&&n.push(ht(h)),ut(n)}function qt(t,e){if(!e.features.length)throw new Error("lines must contain features");if(1===e.features.length)return e.features[0];var n,i=Infinity;return St(e,(function(e){var r=Ft(e,t).properties.dist;r=t[0]&&e[3]>=t[1]}(i,o))return!1;"Polygon"===a&&(s=[s]);for(var l=!1,h=0;ht[1]!=h>t[1]&&t[0]<(l-o)*(t[1]-s)/(h-s)+o&&(i=!i)}return i}function $t(t,e,n,i,r){var a=n[0],o=n[1],s=t[0],l=t[1],h=e[0],u=e[1],c=h-s,p=u-l,d=(n[0]-s)*p-(n[1]-l)*c;if(null!==r){if(Math.abs(d)>r)return!1}else if(0!==d)return!1;return i?"start"===i?Math.abs(c)>=Math.abs(p)?c>0?s0?l=Math.abs(p)?c>0?s<=a&&a0?l<=o&&o=Math.abs(p)?c>0?s0?l=Math.abs(p)?c>0?s<=a&&a<=h:h<=a&&a<=s:p>0?l<=o&&o<=u:u<=o&&o<=l}const Wt=function(t,e,n){void 0===n&&(n={});for(var i=xt(t),r=wt(e),a=0;ae[0])&&(!(t[2]e[1])&&!(t[3]1?e.forEach((function(t){i.push(function(t){return ae({type:"LineString",coordinates:t})}(t))})):i.push(t),i}function pe(t){var e=[];return t.eachLayer((function(t){e.push(se(t.toGeoJSON(15)))})),function(t){return ae({type:"MultiLineString",coordinates:t})}(e)}function de(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var n=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null==n)return;var i,r,a=[],o=!0,s=!1;try{for(n=n.call(t);!(o=(i=n.next()).done)&&(a.push(i.value),!e||a.length!==e);o=!0);}catch(l){s=!0,r=l}finally{try{o||null==n["return"]||n["return"]()}finally{if(s)throw r}}return a}(t,e)||function(t,e){if(!t)return;if("string"==typeof t)return fe(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);"Object"===n&&t.constructor&&(n=t.constructor.name);if("Map"===n||"Set"===n)return Array.from(t);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return fe(t,e)}(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function fe(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);n0)||e.options.layersToCut.indexOf(t)>-1})).filter((function(t){return!e._layerGroup.hasLayer(t)})).filter((function(e){try{var n=!!It(t.toGeoJSON(15),e.toGeoJSON(15)).features.length>0;return n||e instanceof L.Polyline&&!(e instanceof L.Polygon)?n:(i=t.toGeoJSON(15),r=e.toGeoJSON(15),a=oe(i),o=oe(r),!(0===(s=re().intersection(a.coordinates,o.coordinates)).length||!(1===s.length?le(s[0]):he(s))))}catch(l){return e instanceof L.Polygon&&console.error("You can't cut polygons with self-intersections"),!1}var i,r,a,o,s})).forEach((function(n){var r;if(n instanceof L.Polygon){var a=(r=L.polygon(n.getLatLngs())).getLatLngs();i.forEach((function(t){if(t&&t.snapInfo){var n=t.latlng,i=e._calcClosestLayer(n,[r]);if(i&&i.segment&&i.distance1?B()(a,h):a).splice(u,0,n)}}}}))}else r=n;var o=e._cutLayer(t,r),s=L.geoJSON(o,n.options);if(1===s.getLayers().length){var l=s.getLayers();s=de(l,1)[0]}e._setPane(s,"layerPane");var h=s.addTo(e._map.pm._getContainingLayer());if(h.pm.enable(n.pm.options),h.pm.disable(),n._pmTempLayer=!0,t._pmTempLayer=!0,n.remove(),n.removeFrom(e._map.pm._getContainingLayer()),t.remove(),t.removeFrom(e._map.pm._getContainingLayer()),h.getLayers&&0===h.getLayers().length&&e._map.pm.removeLayer({target:h}),h instanceof L.LayerGroup?(h.eachLayer((function(t){e._addDrawnLayerProp(t)})),e._addDrawnLayerProp(h)):e._addDrawnLayerProp(h),e.options.layersToCut&&L.Util.isArray(e.options.layersToCut)&&e.options.layersToCut.length>0){var u=e.options.layersToCut.indexOf(n);u>-1&&e.options.layersToCut.splice(u,1)}e._editedLayers.push({layer:h,originalLayer:n})}))},_cutLayer:function(t,e){var n,i,r,a,o,s,l=L.geoJSON();if(e instanceof L.Polygon)i=e.toGeoJSON(15),r=t.toGeoJSON(15),a=oe(i),o=oe(r),n=0===(s=re().difference(a.coordinates,o.coordinates)).length?null:1===s.length?le(s[0]):he(s);else{var h=ce(e);h.forEach((function(e){var n=Yt(e,t.toGeoJSON(15));(n&&n.features.length>0?L.geoJSON(n):L.geoJSON(e)).getLayers().forEach((function(e){Qt(t.toGeoJSON(15),e.toGeoJSON(15))||e.addTo(l)}))})),n=h.length>1?pe(l):l.toGeoJSON(15)}return n}});const ge={enableLayerDrag:function(){var t;if(this.options.draggable&&this._layer._map){this.disable(),this._layerDragEnabled=!0,this._map||(this._map=this._layer._map),(this._layer instanceof L.Marker||this._layer instanceof L.ImageOverlay)&&L.DomEvent.on(this._getDOMElem(),"dragstart",this._stopDOMImageDrag),this._layer.dragging&&this._layer.dragging.disable(),this._tempDragCoord=null,null!==(t=this._layer._map)&&void 0!==t&&t.options.preferCanvas?(this._layer.on("mouseout",this.removeDraggingClass,this),this._layer.on("mouseover",this.addDraggingClass,this)):this.addDraggingClass(),this._originalMapDragState=this._layer._map.dragging._enabled,this._safeToCacheDragState=!0;var e,n=this._getDOMElem();if(n)null!==(e=this._layer._map)&&void 0!==e&&e.options.preferCanvas&&this._layer._renderer?(this._layer.on("touchstart mousedown",this._dragMixinOnMouseDown,this),this._map.pm._addTouchEvents(n)):L.DomEvent.on(n,"touchstart mousedown",this._simulateMouseDownEvent,this)}},disableLayerDrag:function(){var t;this._layerDragEnabled=!1,null!==(t=this._layer._map)&&void 0!==t&&t.options.preferCanvas?(this._layer.off("mouseout",this.removeDraggingClass,this),this._layer.off("mouseover",this.addDraggingClass,this)):this.removeDraggingClass(),this._originalMapDragState&&this._dragging&&this._map.dragging.enable(),this._safeToCacheDragState=!1,this._layer.dragging&&this._layer.dragging.disable();var e,n=this._getDOMElem();n&&(null!==(e=this._layer._map)&&void 0!==e&&e.options.preferCanvas&&this._layer._renderer?(this._layer.off("touchstart mousedown",this._dragMixinOnMouseDown,this),this._map.pm._removeTouchEvents(n)):L.DomEvent.off(n,"touchstart mousedown",this._simulateMouseDownEvent,this))},dragging:function(){return this._dragging},layerDragEnabled:function(){return!!this._layerDragEnabled},_simulateMouseDownEvent:function(t){var e={originalEvent:t,target:this._layer},n=t.touches?t.touches[0]:t;return e.containerPoint=this._map.mouseEventToContainerPoint(n),e.latlng=this._map.containerPointToLatLng(e.containerPoint),this._dragMixinOnMouseDown(e),!1},_simulateMouseMoveEvent:function(t){var e={originalEvent:t,target:this._layer},n=t.touches?t.touches[0]:t;return e.containerPoint=this._map.mouseEventToContainerPoint(n),e.latlng=this._map.containerPointToLatLng(e.containerPoint),this._dragMixinOnMouseMove(e),!1},_simulateMouseUpEvent:function(t){var e={originalEvent:t,target:this._layer};return-1===t.type.indexOf("touch")&&(e.containerPoint=this._map.mouseEventToContainerPoint(t),e.latlng=this._map.containerPointToLatLng(e.containerPoint)),this._dragMixinOnMouseUp(e),!1},_dragMixinOnMouseDown:function(t){if(!(t.originalEvent.button>0)){this._overwriteEventIfItComesFromMarker(t);var e=t._fromLayerSync,n=this._syncLayers("_dragMixinOnMouseDown",t);this._layer instanceof L.Marker&&(!this.options.snappable||e||n?this._disableSnapping():this._initSnappableMarkers()),this._layer instanceof L.CircleMarker&&!(this._layer instanceof L.Circle)&&(!this.options.snappable||e||n?this._layer.pm.options.editable?this._layer.pm._disableSnapping():this._layer.pm._disableSnappingDrag():this._layer.pm.options.editable||this._initSnappableMarkersDrag()),this._safeToCacheDragState&&(this._originalMapDragState=this._layer._map.dragging._enabled,this._safeToCacheDragState=!1),this._tempDragCoord=t.latlng,L.DomEvent.on(this._map.getContainer(),"touchend mouseup",this._simulateMouseUpEvent,this),L.DomEvent.on(this._map.getContainer(),"touchmove mousemove",this._simulateMouseMoveEvent,this)}},_dragMixinOnMouseMove:function(t){this._overwriteEventIfItComesFromMarker(t);var e=this._getDOMElem();this._syncLayers("_dragMixinOnMouseMove",t),this._dragging||(this._dragging=!0,L.DomUtil.addClass(e,"leaflet-pm-dragging"),this._layer instanceof L.Marker||this._layer.bringToFront(),this._originalMapDragState&&this._map.dragging.disable(),this._fireDragStart()),this._tempDragCoord||(this._tempDragCoord=t.latlng),this._onLayerDrag(t),this._layer instanceof L.CircleMarker&&this._layer.pm._updateHiddenPolyCircle()},_dragMixinOnMouseUp:function(t){var e=this,n=this._getDOMElem();return this._syncLayers("_dragMixinOnMouseUp",t),this._originalMapDragState&&this._map.dragging.enable(),this._safeToCacheDragState=!0,L.DomEvent.off(this._map.getContainer(),"touchmove mousemove",this._simulateMouseMoveEvent,this),L.DomEvent.off(this._map.getContainer(),"touchend mouseup",this._simulateMouseUpEvent,this),!!this._dragging&&(this._layer instanceof L.CircleMarker&&this._layer.pm._updateHiddenPolyCircle(),window.setTimeout((function(){e._dragging=!1,n&&L.DomUtil.removeClass(n,"leaflet-pm-dragging"),e._fireDragEnd(),e._fireEdit(),e._layerEdited=!0}),10),!0)},_onLayerDrag:function(t){var e=t.latlng,n=e.lat-this._tempDragCoord.lat,i=e.lng-this._tempDragCoord.lng,r=function u(t){return t.map((function(t){return Array.isArray(t)?u(t):{lat:t.lat+n,lng:t.lng+i}}))};if(this._layer instanceof L.Circle||this._layer instanceof L.CircleMarker&&this._layer.options.editable){var a=r([this._layer.getLatLng()]);this._layer.setLatLng(a[0])}else if(this._layer instanceof L.CircleMarker||this._layer instanceof L.Marker){var o=this._layer.getLatLng();this._layer._snapped&&(o=this._layer._orgLatLng);var s=r([o]);this._layer.setLatLng(s[0])}else if(this._layer instanceof L.ImageOverlay){var l=r([this._layer.getBounds().getNorthWest(),this._layer.getBounds().getSouthEast()]);this._layer.setBounds(l)}else{var h=r(this._layer.getLatLngs());this._layer.setLatLngs(h)}this._tempDragCoord=e,t.layer=this._layer,this._fireDrag(t)},addDraggingClass:function(){var t=this._getDOMElem();t&&L.DomUtil.addClass(t,"leaflet-pm-draggable")},removeDraggingClass:function(){var t=this._getDOMElem();t&&L.DomUtil.removeClass(t,"leaflet-pm-draggable")},_getDOMElem:function(){var t=null;return this._layer._path?t=this._layer._path:this._layer._renderer&&this._layer._renderer._container?t=this._layer._renderer._container:this._layer._image?t=this._layer._image:this._layer._icon&&(t=this._layer._icon),t},_overwriteEventIfItComesFromMarker:function(t){t.target.getLatLng&&(!t.target._radius||t.target._radius<=10)&&(t.containerPoint=this._map.mouseEventToContainerPoint(t.originalEvent),t.latlng=this._map.containerPointToLatLng(t.containerPoint))},_syncLayers:function(t,e){var n=this;if(this.enabled())return!1;if(!e._fromLayerSync&&this._layer===e.target&&this.options.syncLayersOnDrag){e._fromLayerSync=!0;var i=[];if(L.Util.isArray(this.options.syncLayersOnDrag))i=this.options.syncLayersOnDrag,this.options.syncLayersOnDrag.forEach((function(t){t instanceof L.LayerGroup&&(i=i.concat(t.pm.getLayers(!0)))}));else if(!0===this.options.syncLayersOnDrag&&this._parentLayerGroup)for(var r in this._parentLayerGroup){var a=this._parentLayerGroup[r];a.pm&&(i=a.pm.getLayers(!0))}return L.Util.isArray(i)&&i.length>0&&(i=i.filter((function(t){return!!t.pm})).filter((function(t){return!!t.pm.options.draggable}))).forEach((function(i){i!==n._layer&&i.pm[t]&&(i._snapped=!1,i.pm[t](e))})),i.length>0}return!1},_stopDOMImageDrag:function(t){return t.preventDefault(),!1}};function _e(t,e,n){var i=n.getMaxZoom();if(i===Infinity&&(i=n.getZoom()),L.Util.isArray(t)){var r=[];return t.forEach((function(t){r.push(_e(t,e,n))})),r}return t instanceof L.LatLng?function(t,e,n,i){return n.unproject(e.transform(n.project(t,i)),i)}(t,e,n,i):null}function me(t,e){e instanceof L.Layer&&(e=e.getLatLng());var n=t.getMaxZoom();return n===Infinity&&(n=t.getZoom()),t.project(e,n)}function ye(t,e){var n=t.getMaxZoom();return n===Infinity&&(n=t.getZoom()),t.unproject(e,n)}var ve={_onRotateStart:function(t){this._preventRenderingMarkers(!0),this._rotationOriginLatLng=this._getRotationCenter().clone(),this._rotationOriginPoint=me(this._map,this._rotationOriginLatLng),this._rotationStartPoint=me(this._map,t.target.getLatLng()),this._initialRotateLatLng=V(this._layer),this._startAngle=this.getAngle();var e=V(this._rotationLayer,this._rotationLayer.pm._rotateOrgLatLng);this._fireRotationStart(this._rotationLayer,e),this._fireRotationStart(this._map,e)},_onRotate:function(t){var e=me(this._map,t.target.getLatLng()),n=this._rotationStartPoint,i=this._rotationOriginPoint,r=Math.atan2(e.y-i.y,e.x-i.x)-Math.atan2(n.y-i.y,n.x-i.x);this._layer.setLatLngs(this._rotateLayer(r,this._initialRotateLatLng,this._rotationOriginLatLng,L.PM.Matrix.init(),this._map));var a=this;!function h(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:[],n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:-1;if(n>-1&&e.push(n),L.Util.isArray(t[0]))t.forEach((function(t,n){return h(t,e.slice(),n)}));else{var i=B()(a._markers,e);t.forEach((function(t,e){i[e].setLatLng(t)}))}}(this._layer.getLatLngs());var o=V(this._rotationLayer);this._rotationLayer.setLatLngs(this._rotateLayer(r,this._rotationLayer.pm._rotateOrgLatLng,this._rotationOriginLatLng,L.PM.Matrix.init(),this._map));var s=180*r/Math.PI,l=(s=s<0?s+360:s)+this._startAngle;this._setAngle(l),this._rotationLayer.pm._setAngle(l),this._fireRotation(this._rotationLayer,s,o),this._fireRotation(this._map,s,o)},_onRotateEnd:function(){var t=this._startAngle;delete this._rotationOriginLatLng,delete this._rotationOriginPoint,delete this._rotationStartPoint,delete this._initialRotateLatLng,delete this._startAngle;var e=V(this._rotationLayer,this._rotationLayer.pm._rotateOrgLatLng);this._rotationLayer.pm._rotateOrgLatLng=V(this._rotationLayer),this._fireRotationEnd(this._rotationLayer,t,e),this._fireRotationEnd(this._map,t,e),this._rotationLayer.pm._fireEdit(this._rotationLayer,"Rotation"),this._preventRenderingMarkers(!1)},_rotateLayer:function(t,e,n,i,r){var a=me(r,n);return this._matrix=i.clone().rotate(t,a).flip(),_e(e,this._matrix,r)},_setAngle:function(t){t=t<0?t+360:t,this._angle=t%360},_getRotationCenter:function(){var t=L.polygon(this._layer.getLatLngs(),{stroke:!1,fill:!1,pmIgnore:!0}).addTo(this._layer._map),e=t.getCenter();return t.removeFrom(this._layer._map),e},enableRotate:function(){if(this.options.allowRotation){this._rotatePoly=L.polygon(this._layer.getLatLngs(),{fill:!1,stroke:!1,pmIgnore:!1,snapIgnore:!0}).addTo(this._layer._map),this._rotatePoly.pm._setAngle(this.getAngle()),this._rotatePoly.pm.setOptions(this._layer._map.pm.getGlobalOptions()),this._rotatePoly.pm.setOptions({rotate:!0,snappable:!1,hideMiddleMarkers:!0}),this._rotatePoly.pm._rotationLayer=this._layer,this._rotatePoly.pm.enable(),this._rotateOrgLatLng=V(this._layer),this._rotateEnabled=!0,this._layer.on("remove",this.disableRotate,this),this._fireRotationEnable(this._layer),this._fireRotationEnable(this._layer._map)}else this.disableRotate()},disableRotate:function(){this.rotateEnabled()&&(this._rotatePoly.pm.disable(),this._rotatePoly.remove(),this._rotatePoly.pm.setOptions({rotate:!1}),this._rotatePoly=undefined,this._rotateOrgLatLng=undefined,this._layer.off("remove",this.disableRotate,this),this._rotateEnabled=!1,this._fireRotationDisable(this._layer),this._fireRotationDisable(this._layer._map))},rotateEnabled:function(){return this._rotateEnabled},rotateLayer:function(t){var e=t*(Math.PI/180);this._layer.setLatLngs(this._rotateLayer(e,this._layer.getLatLngs(),this._getRotationCenter(),L.PM.Matrix.init(),this._layer._map)),this._rotateOrgLatLng=L.polygon(this._layer.getLatLngs()).getLatLngs(),this._setAngle(this.getAngle()+t),this.rotateEnabled()&&this._rotatePoly&&this._rotatePoly.pm.enabled()&&(this._rotatePoly.setLatLngs(this._rotateLayer(e,this._rotatePoly.getLatLngs(),this._getRotationCenter(),L.PM.Matrix.init(),this._rotatePoly._map)),this._rotatePoly.pm._initMarkers())},rotateLayerToAngle:function(t){var e=t-this.getAngle();this.rotateLayer(e)},getAngle:function(){return this._angle||0}};const Le=ve;const be=L.Class.extend({includes:[ge,it,Le,S],options:{snappable:!0,snapDistance:20,allowSelfIntersection:!0,allowSelfIntersectionEdit:!1,preventMarkerRemoval:!1,removeLayerBelowMinVertexCount:!0,limitMarkersToCount:-1,hideMiddleMarkers:!1,snapSegment:!0,syncLayersOnDrag:!1,draggable:!0,allowEditing:!0,allowRemoval:!0,allowCutting:!0,allowRotation:!0,addVertexOn:"click",removeVertexOn:"contextmenu",removeVertexValidation:undefined,addVertexValidation:undefined,moveVertexValidation:undefined},setOptions:function(t){L.Util.setOptions(this,t)},getOptions:function(){return this.options},applyOptions:function(){},isPolygon:function(){return this._layer instanceof L.Polygon},getShape:function(){return this._shape},_setPane:function(t,e){"layerPane"===e?t.options.pane=this._map.pm.globalOptions.panes&&this._map.pm.globalOptions.panes.layerPane||"overlayPane":"vertexPane"===e?t.options.pane=this._map.pm.globalOptions.panes&&this._map.pm.globalOptions.panes.vertexPane||"markerPane":"markerPane"===e&&(t.options.pane=this._map.pm.globalOptions.panes&&this._map.pm.globalOptions.panes.markerPane||"markerPane")},remove:function(){(this._map||this._layer._map).pm.removeLayer({target:this._layer})},_vertexValidation:function(t,e){var n=e.target,i={layer:this._layer,marker:n,event:e},r="";return"move"===t?r="moveVertexValidation":"add"===t?r="addVertexValidation":"remove"===t&&(r="removeVertexValidation"),this.options[r]&&"function"==typeof this.options[r]&&!this.options[r](i)?("move"===t&&(n._cancelDragEventChain=n.getLatLng()),!1):(n._cancelDragEventChain=null,!0)},_vertexValidationDrag:function(t){return!t._cancelDragEventChain||(t._latlng=t._cancelDragEventChain,t.update(),!1)},_vertexValidationDragEnd:function(t){return!t._cancelDragEventChain||(t._cancelDragEventChain=null,!1)}});function ke(t){return function(t){if(Array.isArray(t))return Me(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||function(t,e){if(!t)return;if("string"==typeof t)return Me(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);"Object"===n&&t.constructor&&(n=t.constructor.name);if("Map"===n||"Set"===n)return Array.from(t);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return Me(t,e)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function Me(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);n0&&e._getMap()&&e._getMap().pm.globalEditModeEnabled()&&e.enabled()&&e.enable(e.getOptions())}}),100,this),this),this._layerGroup.on("layerremove",(function(t){e._removeLayerFromGroup(t.target)}),this);this._layerGroup.on("layerremove",L.Util.throttle((function(t){t.target._pmTempLayer||(e._layers=e.getLayers())}),100,this),this)},enable:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:[];0===e.length&&(this._layers=this.getLayers()),this._options=t,this._layers.forEach((function(n){n instanceof L.LayerGroup?-1===e.indexOf(n._leaflet_id)&&(e.push(n._leaflet_id),n.pm.enable(t,e)):n.pm.enable(t)}))},disable:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:[];0===t.length&&(this._layers=this.getLayers()),this._layers.forEach((function(e){e instanceof L.LayerGroup?-1===t.indexOf(e._leaflet_id)&&(t.push(e._leaflet_id),e.pm.disable(t)):e.pm.disable()}))},enabled:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:[];0===t.length&&(this._layers=this.getLayers());var e=this._layers.find((function(e){return e instanceof L.LayerGroup?-1===t.indexOf(e._leaflet_id)&&(t.push(e._leaflet_id),e.pm.enabled(t)):e.pm.enabled()}));return!!e},toggleEdit:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:[];0===e.length&&(this._layers=this.getLayers()),this._options=t,this._layers.forEach((function(n){n instanceof L.LayerGroup?-1===e.indexOf(n._leaflet_id)&&(e.push(n._leaflet_id),n.pm.toggleEdit(t,e)):n.pm.toggleEdit(t)}))},_initLayer:function(t){var e=L.Util.stamp(this._layerGroup);t.pm._parentLayerGroup||(t.pm._parentLayerGroup={}),t.pm._parentLayerGroup[e]=this._layerGroup},_removeLayerFromGroup:function(t){if(t.pm&&t.pm._layerGroup){var e=L.Util.stamp(this._layerGroup);delete t.pm._layerGroup[e]}},dragging:function(){if(this._layers=this.getLayers(),this._layers){var t=this._layers.find((function(t){return t.pm.dragging()}));return!!t}return!1},getOptions:function(){return this.options},_getMap:function(){var t;return this._map||(null===(t=this._layers.find((function(t){return!!t._map})))||void 0===t?void 0:t._map)||null},getLayers:function(){var t=arguments.length>0&&arguments[0]!==undefined&&arguments[0],e=!(arguments.length>1&&arguments[1]!==undefined)||arguments[1],n=!(arguments.length>2&&arguments[2]!==undefined)||arguments[2],i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:[],r=[];return t?this._layerGroup.getLayers().forEach((function(t){r.push(t),t instanceof L.LayerGroup&&-1===i.indexOf(t._leaflet_id)&&(i.push(t._leaflet_id),r=r.concat(t.pm.getLayers(!0,!0,!0,i)))})):r=this._layerGroup.getLayers(),n&&(r=r.filter((function(t){return!(t instanceof L.LayerGroup)}))),e&&(r=(r=(r=r.filter((function(t){return!!t.pm}))).filter((function(t){return!t._pmTempLayer}))).filter((function(t){return!L.PM.optIn&&!t.options.pmIgnore||L.PM.optIn&&!1===t.options.pmIgnore}))),r},setOptions:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:[];0===e.length&&(this._layers=this.getLayers()),this.options=t,this._layers.forEach((function(n){n.pm&&(n instanceof L.LayerGroup?-1===e.indexOf(n._leaflet_id)&&(e.push(n._leaflet_id),n.pm.setOptions(t,e)):n.pm.setOptions(t))}))}}),be.Marker=be.extend({_shape:"Marker",initialize:function(t){this._layer=t,this._enabled=!1,this._layer.on("dragend",this._onDragEnd,this)},enable:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{draggable:!0};L.Util.setOptions(this,t),this.options.allowEditing&&this._layer._map?(this._map=this._layer._map,this.enabled()&&this.disable(),this.applyOptions(),this._layer.on("remove",this.disable,this),this._enabled=!0,this._fireEnable()):this.disable()},disable:function(){this.enabled()&&(this.disableLayerDrag(),this._layer.off("remove",this.disable,this),this._layer.off("contextmenu",this._removeMarker,this),this._layerEdited&&this._fireUpdate(),this._layerEdited=!1,this._fireDisable(),this._enabled=!1)},enabled:function(){return this._enabled},toggleEdit:function(t){this.enabled()?this.disable():this.enable(t)},applyOptions:function(){this.options.snappable?this._initSnappableMarkers():this._disableSnapping(),this.options.draggable?this.enableLayerDrag():this.disableLayerDrag(),this.options.preventMarkerRemoval||this._layer.on("contextmenu",this._removeMarker,this)},_removeMarker:function(t){var e=t.target;e.remove(),this._fireRemove(e),this._fireRemove(this._map,e)},_onDragEnd:function(){this._fireEdit(),this._layerEdited=!0},_initSnappableMarkers:function(){var t=this._layer;this.options.snapDistance=this.options.snapDistance||30,this.options.snapSegment=this.options.snapSegment===undefined||this.options.snapSegment,t.off("pm:drag",this._handleSnapping,this),t.on("pm:drag",this._handleSnapping,this),t.off("pm:dragend",this._cleanupSnapping,this),t.on("pm:dragend",this._cleanupSnapping,this),t.off("pm:dragstart",this._unsnap,this),t.on("pm:dragstart",this._unsnap,this)},_disableSnapping:function(){var t=this._layer;t.off("pm:drag",this._handleSnapping,this),t.off("pm:dragend",this._cleanupSnapping,this),t.off("pm:dragstart",this._unsnap,this)}});const xe={filterMarkerGroup:function(){this.markerCache=[],this.createCache(),this._layer.on("pm:edit",this.createCache,this),this.applyLimitFilters({}),this._layer.on("pm:disable",this._removeMarkerLimitEvents,this),this.options.limitMarkersToCount>-1&&(this._layer.on("pm:vertexremoved",this._initMarkers,this),this._map.on("mousemove",this.applyLimitFilters,this))},_removeMarkerLimitEvents:function(){this._map.off("mousemove",this.applyLimitFilters,this),this._layer.off("pm:edit",this.createCache,this),this._layer.off("pm:disable",this._removeMarkerLimitEvents,this),this._layer.off("pm:vertexremoved",this._initMarkers,this)},createCache:function(){var t=[].concat(ke(this._markerGroup.getLayers()),ke(this.markerCache));this.markerCache=t.filter((function(t,e,n){return n.indexOf(t)===e}))},renderLimits:function(t){var e=this;this.markerCache.forEach((function(n){t.includes(n)?e._markerGroup.addLayer(n):e._markerGroup.removeLayer(n)}))},applyLimitFilters:function(t){var e=t.latlng,n=void 0===e?{lat:0,lng:0}:e;if(!this._preventRenderMarkers){var i=ke(this._filterClosestMarkers(n));this.renderLimits(i)}},_filterClosestMarkers:function(t){var e=ke(this.markerCache),n=this.options.limitMarkersToCount;return e.sort((function(e,n){return e._latlng.distanceTo(t)-n._latlng.distanceTo(t)})),e.filter((function(t,e){return!(n>-1)||et.length)&&(e=t.length);for(var n=0,i=new Array(e);n1?B()(r,l):r,u=o.length>1?B()(this._markers,l):this._markers;h.splice(s+1,0,i),u.splice(s+1,0,t),this._layer.setLatLngs(r),!0!==this.options.hideMiddleMarkers&&(this._createMiddleMarker(e,t),this._createMiddleMarker(t,n)),this._fireEdit(),this._layerEdited=!0,this._fireVertexAdded(t,L.PM.Utils.findDeepMarkerIndex(this._markers,t).indexPath,i),this.options.snappable&&this._initSnappableMarkers()},hasSelfIntersection:function(){return _t(this._layer.toGeoJSON(15)).features.length>0},_handleSelfIntersectionOnVertexRemoval:function(){this._handleLayerStyle(!0),this.hasSelfIntersection()&&(this._layer.setLatLngs(this._coordsBeforeEdit),this._coordsBeforeEdit=null,this._initMarkers())},_handleLayerStyle:function(t){var e=this._layer;if(this.hasSelfIntersection()){if(!this.options.allowSelfIntersection&&this.options.allowSelfIntersectionEdit&&this._updateDisabledMarkerStyle(this._markers,!0),this.isRed)return;t?this._flashLayer():(e.setStyle({color:"#f00000ff"}),this.isRed=!0),this._fireIntersect(_t(this._layer.toGeoJSON(15)))}else e.setStyle({color:this.cachedColor}),this.isRed=!1,!this.options.allowSelfIntersection&&this.options.allowSelfIntersectionEdit&&this._updateDisabledMarkerStyle(this._markers,!1)},_flashLayer:function(){var t=this;this.cachedColor||(this.cachedColor=this._layer.options.color),this._layer.setStyle({color:"#f00000ff"}),this.isRed=!0,window.setTimeout((function(){t._layer.setStyle({color:t.cachedColor}),t.isRed=!1}),200)},_updateDisabledMarkerStyle:function(t,e){var n=this;t.forEach((function(t){Array.isArray(t)?n._updateDisabledMarkerStyle(t,e):t._icon&&(e&&!n._checkMarkerAllowedToDrag(t)?L.DomUtil.addClass(t._icon,"vertexmarker-disabled"):L.DomUtil.removeClass(t._icon,"vertexmarker-disabled"))}))},_removeMarker:function(t){var e=t.target;if(this._vertexValidation("remove",t)){if(!this.options.allowSelfIntersection){var n=this._layer.getLatLngs();this._coordsBeforeEdit=JSON.parse(JSON.stringify(n))}var i=this._layer.getLatLngs(),r=L.PM.Utils.findDeepMarkerIndex(this._markers,e),a=r.indexPath,o=r.index,s=r.parentPath;if(a){var l=a.length>1?B()(i,s):i,h=a.length>1?B()(this._markers,s):this._markers;if(this.options.removeLayerBelowMinVertexCount||!(l.length<=2||this.isPolygon()&&l.length<=3)){l.splice(o,1),this._layer.setLatLngs(i),this.isPolygon()&&l.length<=2&&l.splice(0,l.length);var u=!1;if(l.length<=1&&(l.splice(0,l.length),this._layer.setLatLngs(i),this.disable(),this.enable(this.options),u=!0),G(i)&&this._layer.remove(),i=A(i),this._layer.setLatLngs(i),this._markers=A(this._markers),!u&&(h=a.length>1?B()(this._markers,s):this._markers,e._middleMarkerPrev&&this._markerGroup.removeLayer(e._middleMarkerPrev),e._middleMarkerNext&&this._markerGroup.removeLayer(e._middleMarkerNext),this._markerGroup.removeLayer(e),h)){var c,p;if(this.isPolygon()?(c=(o+1)%h.length,p=(o+(h.length-1))%h.length):(p=o-1<0?undefined:o-1,c=o+1>=h.length?undefined:o+1),c!==p){var d=h[p],f=h[c];!0!==this.options.hideMiddleMarkers&&this._createMiddleMarker(d,f)}h.splice(o,1)}this._fireEdit(),this._layerEdited=!0,this._fireVertexRemoved(e,a)}else this._flashLayer()}}},updatePolygonCoordsFromMarkerDrag:function(t){var e=this._layer.getLatLngs(),n=t.getLatLng(),i=L.PM.Utils.findDeepMarkerIndex(this._markers,t),r=i.indexPath,a=i.index,o=i.parentPath;(r.length>1?B()(e,o):e).splice(a,1,n),this._layer.setLatLngs(e)},_getNeighborMarkers:function(t){var e=L.PM.Utils.findDeepMarkerIndex(this._markers,t),n=e.indexPath,i=e.index,r=e.parentPath,a=n.length>1?B()(this._markers,r):this._markers,o=(i+1)%a.length;return{prevMarker:a[(i+(a.length-1))%a.length],nextMarker:a[o]}},_checkMarkerAllowedToDrag:function(t){var e=this._getNeighborMarkers(t),n=e.prevMarker,i=e.nextMarker,r=L.polyline([n.getLatLng(),t.getLatLng()]),a=L.polyline([t.getLatLng(),i.getLatLng()]),o=It(this._layer.toGeoJSON(15),r.toGeoJSON(15)).features.length,s=It(this._layer.toGeoJSON(15),a.toGeoJSON(15)).features.length;return t.getLatLng()===this._markers[0][0].getLatLng()?s+=1:t.getLatLng()===this._markers[0][this._markers[0].length-1].getLatLng()&&(o+=1),!(o<=2&&s<=2)},_onMarkerDragStart:function(t){var e=t.target;if(this.cachedColor||(this.cachedColor=this._layer.options.color),this._vertexValidation("move",t)){var n=L.PM.Utils.findDeepMarkerIndex(this._markers,e).indexPath;this._fireMarkerDragStart(t,n),this.options.allowSelfIntersection||(this._coordsBeforeEdit=this._layer.getLatLngs()),!this.options.allowSelfIntersection&&this.options.allowSelfIntersectionEdit&&this.hasSelfIntersection()?this._markerAllowedToDrag=this._checkMarkerAllowedToDrag(e):this._markerAllowedToDrag=null}},_onMarkerDrag:function(t){var e=t.target;if(this._vertexValidationDrag(e)){var n=L.PM.Utils.findDeepMarkerIndex(this._markers,e),i=n.indexPath,r=n.index,a=n.parentPath;if(i){if(!this.options.allowSelfIntersection&&this.options.allowSelfIntersectionEdit&&this.hasSelfIntersection()&&!1===this._markerAllowedToDrag)return this._layer.setLatLngs(this._coordsBeforeEdit),this._initMarkers(),void this._handleLayerStyle();this.updatePolygonCoordsFromMarkerDrag(e);var o=i.length>1?B()(this._markers,a):this._markers,s=(r+1)%o.length,l=(r+(o.length-1))%o.length,h=e.getLatLng(),u=o[l].getLatLng(),c=o[s].getLatLng();if(e._middleMarkerNext){var p=L.PM.Utils.calcMiddleLatLng(this._map,h,c);e._middleMarkerNext.setLatLng(p)}if(e._middleMarkerPrev){var d=L.PM.Utils.calcMiddleLatLng(this._map,h,u);e._middleMarkerPrev.setLatLng(d)}this.options.allowSelfIntersection||this._handleLayerStyle(),this._fireMarkerDrag(t,i)}}},_onMarkerDragEnd:function(t){var e=t.target;if(this._vertexValidationDragEnd(e)){var n=L.PM.Utils.findDeepMarkerIndex(this._markers,e).indexPath,i=this.hasSelfIntersection();i&&this.options.allowSelfIntersectionEdit&&this._markerAllowedToDrag&&(i=!1);var r=!this.options.allowSelfIntersection&&i;if(this._fireMarkerDragEnd(t,n,r),r)return this._layer.setLatLngs(this._coordsBeforeEdit),this._coordsBeforeEdit=null,this._initMarkers(),this.options.snappable&&this._initSnappableMarkers(),this._handleLayerStyle(),void this._fireLayerReset(t,n);!this.options.allowSelfIntersection&&this.options.allowSelfIntersectionEdit&&this._handleLayerStyle(),this._fireEdit(),this._layerEdited=!0}},_onVertexClick:function(t){var e=t.target;if(!e._dragging){var n=L.PM.Utils.findDeepMarkerIndex(this._markers,e).indexPath;this._fireVertexClick(t,n)}}}),be.Polygon=be.Line.extend({_shape:"Polygon",_checkMarkerAllowedToDrag:function(t){var e=this._getNeighborMarkers(t),n=e.prevMarker,i=e.nextMarker,r=L.polyline([n.getLatLng(),t.getLatLng()]),a=L.polyline([t.getLatLng(),i.getLatLng()]),o=It(this._layer.toGeoJSON(15),r.toGeoJSON(15)).features.length,s=It(this._layer.toGeoJSON(15),a.toGeoJSON(15)).features.length;return!(o<=2&&s<=2)}}),be.Rectangle=be.Polygon.extend({_shape:"Rectangle",_initMarkers:function(){var t=this,e=this._map,n=this._findCorners();this._markerGroup&&this._markerGroup.clearLayers(),this._markerGroup=new L.LayerGroup,this._markerGroup._pmTempLayer=!0,e.addLayer(this._markerGroup),this._markers=[],this._markers[0]=n.map(this._createMarker,this);var i=we(this._markers,1);this._cornerMarkers=i[0],this._layer.getLatLngs()[0].forEach((function(e,n){var i=t._cornerMarkers.find((function(t){return t._index===n}));i&&i.setLatLng(e)}))},applyOptions:function(){this.options.snappable?this._initSnappableMarkers():this._disableSnapping(),this._addMarkerEvents()},_createMarker:function(t,e){var n=new L.Marker(t,{draggable:!0,icon:L.divIcon({className:"marker-icon"})});return this._setPane(n,"vertexPane"),n._origLatLng=t,n._index=e,n._pmTempLayer=!0,this._markerGroup.addLayer(n),n},_addMarkerEvents:function(){var t=this;this._markers[0].forEach((function(e){e.on("dragstart",t._onMarkerDragStart,t),e.on("drag",t._onMarkerDrag,t),e.on("dragend",t._onMarkerDragEnd,t),t.options.preventMarkerRemoval||e.on("contextmenu",t._removeMarker,t)}))},_removeMarker:function(){return null},_onMarkerDragStart:function(t){if(this._vertexValidation("move",t)){var e=t.target,n=this._cornerMarkers;e._oppositeCornerLatLng=n.find((function(t){return t._index===(e._index+2)%4})).getLatLng(),e._snapped=!1,this._fireMarkerDragStart(t)}},_onMarkerDrag:function(t){var e=t.target;this._vertexValidationDrag(e)&&e._index!==undefined&&(this._adjustRectangleForMarkerMove(e),this._fireMarkerDrag(t))},_onMarkerDragEnd:function(t){var e=t.target;this._vertexValidationDragEnd(e)&&(this._cornerMarkers.forEach((function(t){delete t._oppositeCornerLatLng})),this._fireMarkerDragEnd(t),this._fireEdit(),this._layerEdited=!0)},_adjustRectangleForMarkerMove:function(t){L.extend(t._origLatLng,t._latlng);var e=L.PM.Utils._getRotatedRectangle(t.getLatLng(),t._oppositeCornerLatLng,this._angle||0,this._map);this._layer.setLatLngs(e),this._adjustAllMarkers(),this._layer.redraw()},_adjustAllMarkers:function(){var t=this,e=this._layer.getLatLngs()[0];e&&4!==e.length&&e.length>0?(e.forEach((function(e,n){t._cornerMarkers[n].setLatLng(e)})),this._cornerMarkers.slice(e.length).forEach((function(t){t.setLatLng(e[0])}))):e&&e.length?this._cornerMarkers.forEach((function(t){t.setLatLng(e[t._index])})):console.error("The layer has no LatLngs")},_findCorners:function(){var t=this._layer.getLatLngs()[0];return L.PM.Utils._getRotatedRectangle(t[0],t[2],this._angle||0,this._map)}}),be.Circle=be.extend({_shape:"Circle",initialize:function(t){this._layer=t,this._enabled=!1,this._updateHiddenPolyCircle()},enable:function(t){L.Util.setOptions(this,t),this._map=this._layer._map,this.options.allowEditing?(this.enabled()||this.disable(),this._enabled=!0,this._initMarkers(),this.applyOptions(),this._layer.on("remove",this.disable,this),this._updateHiddenPolyCircle(),this._fireEnable()):this.disable()},disable:function(){if(this.enabled()&&!this._dragging){this._centerMarker.off("dragstart",this._onCircleDragStart,this),this._centerMarker.off("drag",this._onCircleDrag,this),this._centerMarker.off("dragend",this._onCircleDragEnd,this),this._outerMarker.off("drag",this._handleOuterMarkerSnapping,this),this._layer.off("remove",this.disable,this),this._enabled=!1,this._helperLayers.clearLayers();var t=this._layer._path?this._layer._path:this._layer._renderer._container;L.DomUtil.removeClass(t,"leaflet-pm-draggable"),this._layerEdited&&this._fireUpdate(),this._layerEdited=!1,this._fireDisable()}},enabled:function(){return this._enabled},toggleEdit:function(t){this.enabled()?this.disable():this.enable(t)},_initMarkers:function(){var t=this._map;this._helperLayers&&this._helperLayers.clearLayers(),this._helperLayers=new L.LayerGroup,this._helperLayers._pmTempLayer=!0,this._helperLayers.addTo(t);var e=this._layer.getLatLng(),n=this._layer._radius,i=this._getLatLngOnCircle(e,n);this._centerMarker=this._createCenterMarker(e),this._outerMarker=this._createOuterMarker(i),this._markers=[this._centerMarker,this._outerMarker],this._createHintLine(this._centerMarker,this._outerMarker)},applyOptions:function(){this.options.snappable?(this._initSnappableMarkers(),this._outerMarker.on("drag",this._handleOuterMarkerSnapping,this),this._outerMarker.on("move",this._syncHintLine,this),this._outerMarker.on("move",this._syncCircleRadius,this),this._centerMarker.on("move",this._moveCircle,this)):this._disableSnapping()},_createHintLine:function(t,e){var n=t.getLatLng(),i=e.getLatLng();this._hintline=L.polyline([n,i],this.options.hintlineStyle),this._setPane(this._hintline,"layerPane"),this._hintline._pmTempLayer=!0,this._helperLayers.addLayer(this._hintline)},_createCenterMarker:function(t){var e=this._createMarker(t);return L.DomUtil.addClass(e._icon,"leaflet-pm-draggable"),e.on("drag",this._moveCircle,this),e.on("dragstart",this._onCircleDragStart,this),e.on("drag",this._onCircleDrag,this),e.on("dragend",this._onCircleDragEnd,this),e},_createOuterMarker:function(t){var e=this._createMarker(t);return e.on("drag",this._resizeCircle,this),e},_createMarker:function(t){var e=new L.Marker(t,{draggable:!0,icon:L.divIcon({className:"marker-icon"})});return this._setPane(e,"vertexPane"),e._origLatLng=t,e._pmTempLayer=!0,e.on("dragstart",this._onMarkerDragStart,this),e.on("drag",this._onMarkerDrag,this),e.on("dragend",this._onMarkerDragEnd,this),this._helperLayers.addLayer(e),e},_resizeCircle:function(){this._outerMarker.setLatLng(this._getNewDestinationOfOuterMarker()),this._syncHintLine(),this._syncCircleRadius()},_moveCircle:function(t){if(!t.target._cancelDragEventChain){var e=t.latlng;this._layer.setLatLng(e);var n=this._layer._radius,i=this._getLatLngOnCircle(e,n);this._outerMarker._latlng=i,this._outerMarker.update(),this._syncHintLine(),this._updateHiddenPolyCircle(),this._fireCenterPlaced("Edit")}},_syncCircleRadius:function(){var t=this._centerMarker.getLatLng(),e=this._outerMarker.getLatLng(),n=this._map.distance(t,e);this.options.minRadiusCircle&&nthis.options.maxRadiusCircle?this._layer.setRadius(this.options.maxRadiusCircle):this._layer.setRadius(n),this._updateHiddenPolyCircle()},_syncHintLine:function(){var t=this._centerMarker.getLatLng(),e=this._outerMarker.getLatLng();this._hintline.setLatLngs([t,e])},_disableSnapping:function(){var t=this;this._markers.forEach((function(e){e.off("move",t._syncHintLine,t),e.off("move",t._syncCircleRadius,t),e.off("drag",t._handleSnapping,t),e.off("dragend",t._cleanupSnapping,t)})),this._layer.off("pm:dragstart",this._unsnap,this)},_onMarkerDragStart:function(t){this._vertexValidation("move",t)&&this._fireMarkerDragStart(t)},_onMarkerDrag:function(t){var e=t.target;this._vertexValidationDrag(e)&&this._fireMarkerDrag(t)},_onMarkerDragEnd:function(t){var e=t.target;this._vertexValidationDragEnd(e)&&(this._fireEdit(),this._layerEdited=!0,this._fireMarkerDragEnd(t))},_onCircleDragStart:function(t){this._vertexValidationDrag(t.target)?(delete this._vertexValidationReset,this._fireDragStart(t)):this._vertexValidationReset=!0},_onCircleDrag:function(t){this._vertexValidationReset||this._fireDrag(t)},_onCircleDragEnd:function(){this._vertexValidationReset?delete this._vertexValidationReset:this._fireDragEnd()},_updateHiddenPolyCircle:function(){var t=this._map&&this._map.pm._isCRSSimple();this._hiddenPolyCircle?this._hiddenPolyCircle.setLatLngs(L.PM.Utils.circleToPolygon(this._layer,200,!t).getLatLngs()):this._hiddenPolyCircle=L.PM.Utils.circleToPolygon(this._layer,200,!t),this._hiddenPolyCircle._parentCopy||(this._hiddenPolyCircle._parentCopy=this._layer)},_getLatLngOnCircle:function(t,e){var n=this._map.project(t),i=L.point(n.x+e,n.y);return this._map.unproject(i)},_getNewDestinationOfOuterMarker:function(){var t=this._centerMarker.getLatLng(),e=this._outerMarker.getLatLng(),n=this._map.distance(t,e);return this.options.minRadiusCircle&&nthis.options.maxRadiusCircle&&(e=U(this._map,t,e,this.options.maxRadiusCircle)),e},_handleOuterMarkerSnapping:function(){if(this._outerMarker._snapped){var t=this._centerMarker.getLatLng(),e=this._outerMarker.getLatLng(),n=this._map.distance(t,e);(this.options.minRadiusCircle&&nthis.options.maxRadiusCircle)&&this._outerMarker.setLatLng(this._outerMarker._orgLatLng)}this._outerMarker.setLatLng(this._getNewDestinationOfOuterMarker())}}),be.CircleMarker=be.extend({_shape:"CircleMarker",initialize:function(t){this._layer=t,this._enabled=!1,this._updateHiddenPolyCircle()},enable:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{draggable:!0,snappable:!0};L.Util.setOptions(this,t),this.options.allowEditing&&this._layer._map?(this._map=this._layer._map,this.enabled()&&this.disable(),this.applyOptions(),this._layer.on("remove",this.disable,this),this._enabled=!0,this._layer.on("pm:dragstart",this._onDragStart,this),this._layer.on("pm:drag",this._onMarkerDrag,this),this._layer.on("pm:dragend",this._onMarkerDragEnd,this),this._updateHiddenPolyCircle(),this._fireEnable()):this.disable()},disable:function(){this._dragging||(this._helperLayers&&this._helperLayers.clearLayers(),this._map||(this._map=this._layer._map),this._map||(this.options.editable?(this._map.off("move",this._syncMarkers,this),this._outerMarker&&this._outerMarker.on("drag",this._handleOuterMarkerSnapping,this)):this._map.off("move",this._updateHiddenPolyCircle,this)),this.disableLayerDrag(),this._layer.off("contextmenu",this._removeMarker,this),this._layer.off("remove",this.disable,this),this.enabled()&&(this._layerEdited&&this._fireUpdate(),this._layerEdited=!1,this._fireDisable()),this._enabled=!1)},enabled:function(){return this._enabled},toggleEdit:function(t){this.enabled()?this.disable():this.enable(t)},applyOptions:function(){!this.options.editable&&this.options.draggable?this.enableLayerDrag():this.disableLayerDrag(),this.options.editable?(this._initMarkers(),this._map.on("move",this._syncMarkers,this)):this._map.on("move",this._updateHiddenPolyCircle,this),this.options.snappable?this.options.editable?(this._initSnappableMarkers(),this._centerMarker.on("drag",this._moveCircle,this),this.options.editable&&this._outerMarker.on("drag",this._handleOuterMarkerSnapping,this),this._outerMarker.on("move",this._syncHintLine,this),this._outerMarker.on("move",this._syncCircleRadius,this)):this._initSnappableMarkersDrag():this.options.editable?this._disableSnapping():this._disableSnappingDrag(),this.options.preventMarkerRemoval||this._layer.on("contextmenu",this._removeMarker,this)},_initMarkers:function(){var t=this._map;this._helperLayers&&this._helperLayers.clearLayers(),this._helperLayers=new L.LayerGroup,this._helperLayers._pmTempLayer=!0,this._helperLayers.addTo(t);var e=this._layer.getLatLng(),n=this._layer._radius,i=this._getLatLngOnCircle(e,n);this._centerMarker=this._createCenterMarker(e),this._outerMarker=this._createOuterMarker(i),this._markers=[this._centerMarker,this._outerMarker],this._createHintLine(this._centerMarker,this._outerMarker)},_getLatLngOnCircle:function(t,e){var n=this._map.project(t),i=L.point(n.x+e,n.y);return this._map.unproject(i)},_createHintLine:function(t,e){var n=t.getLatLng(),i=e.getLatLng();this._hintline=L.polyline([n,i],this.options.hintlineStyle),this._setPane(this._hintline,"layerPane"),this._hintline._pmTempLayer=!0,this._helperLayers.addLayer(this._hintline)},_createCenterMarker:function(t){var e=this._createMarker(t);return this.options.draggable?L.DomUtil.addClass(e._icon,"leaflet-pm-draggable"):e.dragging.disable(),e},_createOuterMarker:function(t){var e=this._createMarker(t);return e.on("drag",this._resizeCircle,this),e},_createMarker:function(t){var e=new L.Marker(t,{draggable:!0,icon:L.divIcon({className:"marker-icon"})});return this._setPane(e,"vertexPane"),e._origLatLng=t,e._pmTempLayer=!0,e.on("dragstart",this._onMarkerDragStart,this),e.on("drag",this._onMarkerDrag,this),e.on("dragend",this._onMarkerDragEnd,this),this._helperLayers.addLayer(e),e},_moveCircle:function(){var t=this._centerMarker.getLatLng();this._layer.setLatLng(t);var e=this._layer._radius,n=this._getLatLngOnCircle(t,e);this._outerMarker._latlng=n,this._outerMarker.update(),this._syncHintLine(),this._updateHiddenPolyCircle(),this._fireCenterPlaced("Edit")},_syncMarkers:function(){var t=this._layer.getLatLng(),e=this._layer._radius,n=this._getLatLngOnCircle(t,e);this._outerMarker.setLatLng(n),this._centerMarker.setLatLng(t),this._syncHintLine(),this._updateHiddenPolyCircle()},_resizeCircle:function(){this._outerMarker.setLatLng(this._getNewDestinationOfOuterMarker()),this._syncHintLine(),this._syncCircleRadius()},_syncCircleRadius:function(){var t=this._centerMarker.getLatLng(),e=this._outerMarker.getLatLng(),n=this._map.project(t).distanceTo(this._map.project(e));this.options.minRadiusCircleMarker&&nthis.options.maxRadiusCircleMarker?this._layer.setRadius(this.options.maxRadiusCircleMarker):this._layer.setRadius(n),this._updateHiddenPolyCircle()},_syncHintLine:function(){var t=this._centerMarker.getLatLng(),e=this._outerMarker.getLatLng();this._hintline.setLatLngs([t,e])},_removeMarker:function(){this.options.editable&&this.disable(),this._layer.remove(),this._fireRemove(this._layer),this._fireRemove(this._map,this._layer)},_onDragStart:function(){this._map.pm.Draw.CircleMarker._layerIsDragging=!0},_onMarkerDragStart:function(t){this._vertexValidation("move",t)&&this._fireMarkerDragStart(t)},_onMarkerDrag:function(t){var e=t.target;e instanceof L.Marker&&!this._vertexValidationDrag(e)||this._fireMarkerDrag(t)},_onMarkerDragEnd:function(t){this._map.pm.Draw.CircleMarker._layerIsDragging=!1;var e=t.target;this._vertexValidationDragEnd(e)&&(this.options.editable&&(this._fireEdit(),this._layerEdited=!0),this._fireMarkerDragEnd(t))},_initSnappableMarkersDrag:function(){var t=this._layer;this.options.snapDistance=this.options.snapDistance||30,this.options.snapSegment=this.options.snapSegment===undefined||this.options.snapSegment,t.off("pm:drag",this._handleSnapping,this),t.on("pm:drag",this._handleSnapping,this),t.off("pm:dragend",this._cleanupSnapping,this),t.on("pm:dragend",this._cleanupSnapping,this),t.off("pm:dragstart",this._unsnap,this),t.on("pm:dragstart",this._unsnap,this)},_disableSnappingDrag:function(){var t=this._layer;t.off("pm:drag",this._handleSnapping,this),t.off("pm:dragend",this._cleanupSnapping,this),t.off("pm:dragstart",this._unsnap,this)},_updateHiddenPolyCircle:function(){var t=this._layer._map||this._map;if(t){var e=L.PM.Utils.pxRadiusToMeterRadius(this._layer.getRadius(),t,this._layer.getLatLng()),n=L.circle(this._layer.getLatLng(),this._layer.options);n.setRadius(e);var i=t&&t.pm._isCRSSimple();this._hiddenPolyCircle?this._hiddenPolyCircle.setLatLngs(L.PM.Utils.circleToPolygon(n,200,!i).getLatLngs()):this._hiddenPolyCircle=L.PM.Utils.circleToPolygon(n,200,!i),this._hiddenPolyCircle._parentCopy||(this._hiddenPolyCircle._parentCopy=this._layer)}},_getNewDestinationOfOuterMarker:function(){var t=this._centerMarker.getLatLng(),e=this._outerMarker.getLatLng(),n=this._map.project(t).distanceTo(this._map.project(e));return this.options.minRadiusCircleMarker&&nthis.options.maxRadiusCircleMarker&&(e=U(this._map,t,e,L.PM.Utils.pxRadiusToMeterRadius(this.options.maxRadiusCircleMarker,this._map,t))),e},_handleOuterMarkerSnapping:function(){if(this._outerMarker._snapped){var t=this._centerMarker.getLatLng(),e=this._outerMarker.getLatLng(),n=this._map.project(t).distanceTo(this._map.project(e));(this.options.minRadiusCircleMarker&&nthis.options.maxRadiusCircleMarker)&&this._outerMarker.setLatLng(this._outerMarker._orgLatLng)}this._outerMarker.setLatLng(this._getNewDestinationOfOuterMarker())}}),be.ImageOverlay=be.extend({_shape:"ImageOverlay",initialize:function(t){this._layer=t,this._enabled=!1},toggleEdit:function(t){this.enabled()?this.disable():this.enable(t)},enabled:function(){return this._enabled},enable:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{draggable:!0,snappable:!0};L.Util.setOptions(this,t),this._map=this._layer._map,this._map&&(this.options.allowEditing?(this.enabled()||this.disable(),this.enableLayerDrag(),this._layer.on("remove",this.disable,this),this._enabled=!0,this._otherSnapLayers=this._findCorners(),this._fireEnable()):this.disable())},disable:function(){this._dragging||(this._map||(this._map=this._layer._map),this.disableLayerDrag(),this._layer.off("remove",this.disable,this),this.enabled()||(this._layerEdited&&this._fireUpdate(),this._layerEdited=!1,this._fireDisable()),this._enabled=!1)},_findCorners:function(){var t=this._layer.getBounds();return[t.getNorthWest(),t.getNorthEast(),t.getSouthEast(),t.getSouthWest()]}});var Pe=function(t,e,n,i,r,a){this._matrix=[t,e,n,i,r,a]};Pe.init=function(){return new L.PM.Matrix(1,0,0,1,0,0)},Pe.prototype={transform:function(t){return this._transform(t.clone())},_transform:function(t){var e=this._matrix,n=t.x,i=t.y;return t.x=e[0]*n+e[1]*i+e[4],t.y=e[2]*n+e[3]*i+e[5],t},untransform:function(t){var e=this._matrix;return new L.Point((t.x/e[0]-e[4])/e[0],(t.y/e[2]-e[5])/e[2])},clone:function(){var t=this._matrix;return new L.PM.Matrix(t[0],t[1],t[2],t[3],t[4],t[5])},translate:function(t){return t===undefined?new L.Point(this._matrix[4],this._matrix[5]):("number"==typeof t?(e=t,n=t):(e=t.x,n=t.y),this._add(1,0,0,1,e,n));var e,n},scale:function(t,e){return t===undefined?new L.Point(this._matrix[0],this._matrix[3]):(e=e||L.point(0,0),"number"==typeof t?(n=t,i=t):(n=t.x,i=t.y),this._add(n,0,0,i,e.x,e.y)._add(1,0,0,1,-e.x,-e.y));var n,i},rotate:function(t,e){var n=Math.cos(t),i=Math.sin(t);return e=e||new L.Point(0,0),this._add(n,i,-i,n,e.x,e.y)._add(1,0,0,1,-e.x,-e.y)},flip:function(){return this._matrix[1]*=-1,this._matrix[2]*=-1,this},_add:function(t,e,n,i,r,a){var o,s=[[],[],[]],l=this._matrix,h=[[l[0],l[2],l[4]],[l[1],l[3],l[5]],[0,0,1]],u=[[t,n,r],[e,i,a],[0,0,1]];t&&t instanceof L.PM.Matrix&&(u=[[(l=t._matrix)[0],l[2],l[4]],[l[1],l[3],l[5]],[0,0,1]]);for(var c=0;c<3;c+=1)for(var p=0;p<3;p+=1){o=0;for(var d=0;d<3;d+=1)o+=h[c][d]*u[d][p];s[c][p]=o}return this._matrix=[s[0][0],s[1][0],s[0][1],s[1][1],s[0][2],s[1][2]],this}};const Ee=Pe;var Se={calcMiddleLatLng:function(t,e,n){var i=t.project(e),r=t.project(n);return t.unproject(i._add(r)._divideBy(2))},findLayers:function(t){var e=[];return t.eachLayer((function(t){(t instanceof L.Polyline||t instanceof L.Marker||t instanceof L.Circle||t instanceof L.CircleMarker||t instanceof L.ImageOverlay)&&e.push(t)})),e=(e=(e=e.filter((function(t){return!!t.pm}))).filter((function(t){return!t._pmTempLayer}))).filter((function(t){return!L.PM.optIn&&!t.options.pmIgnore||L.PM.optIn&&!1===t.options.pmIgnore}))},circleToPolygon:function(t){for(var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:60,n=!(arguments.length>2&&arguments[2]!==undefined)||arguments[2],i=t.getLatLng(),r=t.getRadius(),a=z(i,r,e,0,n),o=[],s=0;s3&&arguments[3]!==undefined&&arguments[3];t.fire(e,n,i);var r=this.getAllParentGroups(t),a=r.groups;a.forEach((function(t){t.fire(e,n,i)}))},getAllParentGroups:function(t){var e=[],n=[];return!t._pmLastGroupFetch||!t._pmLastGroupFetch.time||(new Date).getTime()-t._pmLastGroupFetch.time>1e3?(function i(t){for(var r in t._eventParents)if(-1===e.indexOf(r)){e.push(r);var a=t._eventParents[r];n.push(a),i(a)}}(t),t._pmLastGroupFetch={time:(new Date).getTime(),groups:n,groupIds:e},{groupIds:e,groups:n}):{groups:t._pmLastGroupFetch.groups,groupIds:t._pmLastGroupFetch.groupIds}},createGeodesicPolygon:z,getTranslation:j,findDeepCoordIndex:function(t,e){var n;t.some(function r(t){return function(i,a){var o=t.concat(a);return i.lat&&i.lat===e.lat&&i.lng===e.lng?(n=o,!0):Array.isArray(i)&&i.some(r(o))}}([]));var i={};return n&&(i={indexPath:n,index:n[n.length-1],parentPath:n.slice(0,n.length-1)}),i},findDeepMarkerIndex:function(t,e){var n;t.some(function r(t){return function(i,a){var o=t.concat(a);return i._leaflet_id===e._leaflet_id?(n=o,!0):Array.isArray(i)&&i.some(r(o))}}([]));var i={};return n&&(i={indexPath:n,index:n[n.length-1],parentPath:n.slice(0,n.length-1)}),i},_getIndexFromSegment:function(t,e){if(e&&2===e.length){var n=this.findDeepCoordIndex(t,e[0]),i=this.findDeepCoordIndex(t,e[1]),r=Math.max(n.index,i.index);return 0!==n.index&&0!==i.index||1===r||(r+=1),{indexA:n,indexB:i,newIndex:r,indexPath:n.indexPath,parentPath:n.parentPath}}return null},_getRotatedRectangle:function(t,e,n,i){var r=me(i,t),a=me(i,e),o=n*Math.PI/180,s=Math.cos(o),l=Math.sin(o),h=(a.x-r.x)*s+(a.y-r.y)*l,u=(a.y-r.y)*s-(a.x-r.x)*l,c=h*s+r.x,p=h*l+r.y,d=-u*l+r.x,f=u*s+r.y;return[ye(i,r),ye(i,{x:c,y:p}),ye(i,a),ye(i,{x:d,y:f})]},pxRadiusToMeterRadius:function(t,e,n){var i=e.project(n),r=L.point(i.x+t,i.y);return e.distance(e.unproject(r),n)}};const Oe=Se;L.PM=L.PM||{version:"2.11.4",Map:D,Toolbar:W,Draw:rt,Edit:be,Utils:Oe,Matrix:Ee,activeLang:"en",optIn:!1,initialize:function(t){this.addInitHooks(t)},setOptIn:function(t){this.optIn=!!t},addInitHooks:function(){L.Map.addInitHook((function(){this.pm=undefined,L.PM.optIn?!1===this.options.pmIgnore&&(this.pm=new L.PM.Map(this)):this.options.pmIgnore||(this.pm=new L.PM.Map(this))})),L.LayerGroup.addInitHook((function(){this.pm=undefined,L.PM.optIn?!1===this.options.pmIgnore&&(this.pm=new L.PM.Edit.LayerGroup(this)):this.options.pmIgnore||(this.pm=new L.PM.Edit.LayerGroup(this))})),L.Marker.addInitHook((function(){this.pm=undefined,L.PM.optIn?!1===this.options.pmIgnore&&(this.pm=new L.PM.Edit.Marker(this)):this.options.pmIgnore||(this.pm=new L.PM.Edit.Marker(this))})),L.CircleMarker.addInitHook((function(){this.pm=undefined,L.PM.optIn?!1===this.options.pmIgnore&&(this.pm=new L.PM.Edit.CircleMarker(this)):this.options.pmIgnore||(this.pm=new L.PM.Edit.CircleMarker(this))})),L.Polyline.addInitHook((function(){this.pm=undefined,L.PM.optIn?!1===this.options.pmIgnore&&(this.pm=new L.PM.Edit.Line(this)):this.options.pmIgnore||(this.pm=new L.PM.Edit.Line(this))})),L.Polygon.addInitHook((function(){this.pm=undefined,L.PM.optIn?!1===this.options.pmIgnore&&(this.pm=new L.PM.Edit.Polygon(this)):this.options.pmIgnore||(this.pm=new L.PM.Edit.Polygon(this))})),L.Rectangle.addInitHook((function(){this.pm=undefined,L.PM.optIn?!1===this.options.pmIgnore&&(this.pm=new L.PM.Edit.Rectangle(this)):this.options.pmIgnore||(this.pm=new L.PM.Edit.Rectangle(this))})),L.Circle.addInitHook((function(){this.pm=undefined,L.PM.optIn?!1===this.options.pmIgnore&&(this.pm=new L.PM.Edit.Circle(this)):this.options.pmIgnore||(this.pm=new L.PM.Edit.Circle(this))})),L.ImageOverlay.addInitHook((function(){this.pm=undefined,L.PM.optIn?!1===this.options.pmIgnore&&(this.pm=new L.PM.Edit.ImageOverlay(this)):this.options.pmIgnore||(this.pm=new L.PM.Edit.ImageOverlay(this))}))},reInitLayer:function(t){var e=this;t instanceof L.LayerGroup&&t.eachLayer((function(t){e.reInitLayer(t)})),t.pm||L.PM.optIn&&!1!==t.options.pmIgnore||t.options.pmIgnore||(t instanceof L.Map?t.pm=new L.PM.Map(t):t instanceof L.Marker?t.pm=new L.PM.Edit.Marker(t):t instanceof L.Circle?t.pm=new L.PM.Edit.Circle(t):t instanceof L.CircleMarker?t.pm=new L.PM.Edit.CircleMarker(t):t instanceof L.Rectangle?t.pm=new L.PM.Edit.Rectangle(t):t instanceof L.Polygon?t.pm=new L.PM.Edit.Polygon(t):t instanceof L.Polyline?t.pm=new L.PM.Edit.Line(t):t instanceof L.LayerGroup?t.pm=new L.PM.Edit.LayerGroup(t):t instanceof L.ImageOverlay&&(t.pm=new L.PM.Edit.ImageOverlay(t)))}},"1.7.1"===L.version&&L.Canvas.include({_onClick:function(t){for(var e,n,i=this._map.mouseEventToLayerPoint(t),r=this._drawFirst;r;r=r.next)(e=r.layer).options.interactive&&e._containsPoint(i)&&("click"!==t.type&&"preclick"!==t.type||!this._map._draggableMoved(e))&&(n=e);n&&(L.DomEvent.fakeStop(t),this._fireEvent([n],t))}}),L.PM.initialize()},7107:()=>{Array.prototype.findIndex=Array.prototype.findIndex||function(t){if(null===this)throw new TypeError("Array.prototype.findIndex called on null or undefined");if("function"!=typeof t)throw new TypeError("callback must be a function");for(var e=Object(this),n=e.length>>>0,i=arguments[1],r=0;r>>0,i=arguments[1],r=0;r>>0;if(0===i)return!1;var r,a,o=0|e,s=Math.max(o>=0?o:i-Math.abs(o),0);for(;s{var i=n(2582),r=n(4102),a=n(1540),o=n(9705).Z,s=a.featureEach,l=(a.coordEach,r.polygon,r.featureCollection);function h(t){var e=new i(t);return e.insert=function(t){if("Feature"!==t.type)throw new Error("invalid feature");return t.bbox=t.bbox?t.bbox:o(t),i.prototype.insert.call(this,t)},e.load=function(t){var e=[];return Array.isArray(t)?t.forEach((function(t){if("Feature"!==t.type)throw new Error("invalid features");t.bbox=t.bbox?t.bbox:o(t),e.push(t)})):s(t,(function(t){if("Feature"!==t.type)throw new Error("invalid features");t.bbox=t.bbox?t.bbox:o(t),e.push(t)})),i.prototype.load.call(this,e)},e.remove=function(t,e){if("Feature"!==t.type)throw new Error("invalid feature");return t.bbox=t.bbox?t.bbox:o(t),i.prototype.remove.call(this,t,e)},e.clear=function(){return i.prototype.clear.call(this)},e.search=function(t){var e=i.prototype.search.call(this,this.toBBox(t));return l(e)},e.collides=function(t){return i.prototype.collides.call(this,this.toBBox(t))},e.all=function(){var t=i.prototype.all.call(this);return l(t)},e.toJSON=function(){return i.prototype.toJSON.call(this)},e.fromJSON=function(t){return i.prototype.fromJSON.call(this,t)},e.toBBox=function(t){var e;if(t.bbox)e=t.bbox;else if(Array.isArray(t)&&4===t.length)e=t;else if(Array.isArray(t)&&6===t.length)e=[t[0],t[1],t[3],t[4]];else if("Feature"===t.type)e=o(t);else{if("FeatureCollection"!==t.type)throw new Error("invalid geojson");e=o(t)}return{minX:e[0],minY:e[1],maxX:e[2],maxY:e[3]}},e}t.exports=h,t.exports["default"]=h},1989:(t,e,n)=>{var i=n(1789),r=n(401),a=n(7667),o=n(1327),s=n(1866);function l(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e{var i=n(7040),r=n(4125),a=n(2117),o=n(7518),s=n(4705);function l(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e{var i=n(852)(n(5639),"Map");t.exports=i},3369:(t,e,n)=>{var i=n(4785),r=n(1285),a=n(6e3),o=n(9916),s=n(5265);function l(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e{var i=n(8407),r=n(7465),a=n(3779),o=n(7599),s=n(4758),l=n(4309);function h(t){var e=this.__data__=new i(t);this.size=e.size}h.prototype.clear=r,h.prototype["delete"]=a,h.prototype.get=o,h.prototype.has=s,h.prototype.set=l,t.exports=h},2705:(t,e,n)=>{var i=n(5639).Symbol;t.exports=i},1149:(t,e,n)=>{var i=n(5639).Uint8Array;t.exports=i},6874:t=>{t.exports=function(t,e,n){switch(n.length){case 0:return t.call(e);case 1:return t.call(e,n[0]);case 2:return t.call(e,n[0],n[1]);case 3:return t.call(e,n[0],n[1],n[2])}return t.apply(e,n)}},4636:(t,e,n)=>{var i=n(2545),r=n(5694),a=n(1469),o=n(4144),s=n(5776),l=n(6719),h=Object.prototype.hasOwnProperty;t.exports=function(t,e){var n=a(t),u=!n&&r(t),c=!n&&!u&&o(t),p=!n&&!u&&!c&&l(t),d=n||u||c||p,f=d?i(t.length,String):[],g=f.length;for(var _ in t)!e&&!h.call(t,_)||d&&("length"==_||c&&("offset"==_||"parent"==_)||p&&("buffer"==_||"byteLength"==_||"byteOffset"==_)||s(_,g))||f.push(_);return f}},9932:t=>{t.exports=function(t,e){for(var n=-1,i=null==t?0:t.length,r=Array(i);++n{var i=n(9465),r=n(7813);t.exports=function(t,e,n){(n!==undefined&&!r(t[e],n)||n===undefined&&!(e in t))&&i(t,e,n)}},4865:(t,e,n)=>{var i=n(9465),r=n(7813),a=Object.prototype.hasOwnProperty;t.exports=function(t,e,n){var o=t[e];a.call(t,e)&&r(o,n)&&(n!==undefined||e in t)||i(t,e,n)}},8470:(t,e,n)=>{var i=n(7813);t.exports=function(t,e){for(var n=t.length;n--;)if(i(t[n][0],e))return n;return-1}},9465:(t,e,n)=>{var i=n(8777);t.exports=function(t,e,n){"__proto__"==e&&i?i(t,e,{configurable:!0,enumerable:!0,value:n,writable:!0}):t[e]=n}},3118:(t,e,n)=>{var i=n(3218),r=Object.create,a=function(){function t(){}return function(e){if(!i(e))return{};if(r)return r(e);t.prototype=e;var n=new t;return t.prototype=undefined,n}}();t.exports=a},8483:(t,e,n)=>{var i=n(5063)();t.exports=i},7786:(t,e,n)=>{var i=n(1811),r=n(327);t.exports=function(t,e){for(var n=0,a=(e=i(e,t)).length;null!=t&&n{var i=n(2705),r=n(9607),a=n(2333),o=i?i.toStringTag:undefined;t.exports=function(t){return null==t?t===undefined?"[object Undefined]":"[object Null]":o&&o in Object(t)?r(t):a(t)}},8565:t=>{var e=Object.prototype.hasOwnProperty;t.exports=function(t,n){return null!=t&&e.call(t,n)}},9454:(t,e,n)=>{var i=n(4239),r=n(7005);t.exports=function(t){return r(t)&&"[object Arguments]"==i(t)}},8458:(t,e,n)=>{var i=n(3560),r=n(5346),a=n(3218),o=n(346),s=/^\[object .+?Constructor\]$/,l=Function.prototype,h=Object.prototype,u=l.toString,c=h.hasOwnProperty,p=RegExp("^"+u.call(c).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");t.exports=function(t){return!(!a(t)||r(t))&&(i(t)?p:s).test(o(t))}},8749:(t,e,n)=>{var i=n(4239),r=n(1780),a=n(7005),o={};o["[object Float32Array]"]=o["[object Float64Array]"]=o["[object Int8Array]"]=o["[object Int16Array]"]=o["[object Int32Array]"]=o["[object Uint8Array]"]=o["[object Uint8ClampedArray]"]=o["[object Uint16Array]"]=o["[object Uint32Array]"]=!0,o["[object Arguments]"]=o["[object Array]"]=o["[object ArrayBuffer]"]=o["[object Boolean]"]=o["[object DataView]"]=o["[object Date]"]=o["[object Error]"]=o["[object Function]"]=o["[object Map]"]=o["[object Number]"]=o["[object Object]"]=o["[object RegExp]"]=o["[object Set]"]=o["[object String]"]=o["[object WeakMap]"]=!1,t.exports=function(t){return a(t)&&r(t.length)&&!!o[i(t)]}},313:(t,e,n)=>{var i=n(3218),r=n(5726),a=n(3498),o=Object.prototype.hasOwnProperty;t.exports=function(t){if(!i(t))return a(t);var e=r(t),n=[];for(var s in t)("constructor"!=s||!e&&o.call(t,s))&&n.push(s);return n}},2980:(t,e,n)=>{var i=n(6384),r=n(6556),a=n(8483),o=n(9783),s=n(3218),l=n(1704),h=n(6390);t.exports=function u(t,e,n,c,p){t!==e&&a(e,(function(a,l){if(p||(p=new i),s(a))o(t,e,l,n,u,c,p);else{var d=c?c(h(t,l),a,l+"",t,e,p):undefined;d===undefined&&(d=a),r(t,l,d)}}),l)}},9783:(t,e,n)=>{var i=n(6556),r=n(4626),a=n(7133),o=n(278),s=n(8517),l=n(5694),h=n(1469),u=n(9246),c=n(4144),p=n(3560),d=n(3218),f=n(8630),g=n(6719),_=n(6390),m=n(9881);t.exports=function(t,e,n,y,v,L,b){var k=_(t,n),M=_(e,n),x=b.get(M);if(x)i(t,n,x);else{var w=L?L(k,M,n+"",t,e,b):undefined,C=w===undefined;if(C){var P=h(M),E=!P&&c(M),S=!P&&!E&&g(M);w=M,P||E||S?h(k)?w=k:u(k)?w=o(k):E?(C=!1,w=r(M,!0)):S?(C=!1,w=a(M,!0)):w=[]:f(M)||l(M)?(w=k,l(k)?w=m(k):d(k)&&!p(k)||(w=s(M))):C=!1}C&&(b.set(M,w),v(w,M,y,L,b),b["delete"](M)),i(t,n,w)}}},5976:(t,e,n)=>{var i=n(6557),r=n(5357),a=n(61);t.exports=function(t,e){return a(r(t,e,i),t+"")}},6560:(t,e,n)=>{var i=n(5703),r=n(8777),a=n(6557),o=r?function(t,e){return r(t,"toString",{configurable:!0,enumerable:!1,value:i(e),writable:!0})}:a;t.exports=o},2545:t=>{t.exports=function(t,e){for(var n=-1,i=Array(t);++n{var i=n(2705),r=n(9932),a=n(1469),o=n(3448),s=i?i.prototype:undefined,l=s?s.toString:undefined;t.exports=function h(t){if("string"==typeof t)return t;if(a(t))return r(t,h)+"";if(o(t))return l?l.call(t):"";var e=t+"";return"0"==e&&1/t==-Infinity?"-0":e}},1717:t=>{t.exports=function(t){return function(e){return t(e)}}},1811:(t,e,n)=>{var i=n(1469),r=n(5403),a=n(5514),o=n(9833);t.exports=function(t,e){return i(t)?t:r(t,e)?[t]:a(o(t))}},4318:(t,e,n)=>{var i=n(1149);t.exports=function(t){var e=new t.constructor(t.byteLength);return new i(e).set(new i(t)),e}},4626:(t,e,n)=>{t=n.nmd(t);var i=n(5639),r=e&&!e.nodeType&&e,a=r&&t&&!t.nodeType&&t,o=a&&a.exports===r?i.Buffer:undefined,s=o?o.allocUnsafe:undefined;t.exports=function(t,e){if(e)return t.slice();var n=t.length,i=s?s(n):new t.constructor(n);return t.copy(i),i}},7133:(t,e,n)=>{var i=n(4318);t.exports=function(t,e){var n=e?i(t.buffer):t.buffer;return new t.constructor(n,t.byteOffset,t.length)}},278:t=>{t.exports=function(t,e){var n=-1,i=t.length;for(e||(e=Array(i));++n{var i=n(4865),r=n(9465);t.exports=function(t,e,n,a){var o=!n;n||(n={});for(var s=-1,l=e.length;++s{var i=n(5639)["__core-js_shared__"];t.exports=i},1463:(t,e,n)=>{var i=n(5976),r=n(6612);t.exports=function(t){return i((function(e,n){var i=-1,a=n.length,o=a>1?n[a-1]:undefined,s=a>2?n[2]:undefined;for(o=t.length>3&&"function"==typeof o?(a--,o):undefined,s&&r(n[0],n[1],s)&&(o=a<3?undefined:o,a=1),e=Object(e);++i{t.exports=function(t){return function(e,n,i){for(var r=-1,a=Object(e),o=i(e),s=o.length;s--;){var l=o[t?s:++r];if(!1===n(a[l],l,a))break}return e}}},8777:(t,e,n)=>{var i=n(852),r=function(){try{var t=i(Object,"defineProperty");return t({},"",{}),t}catch(e){}}();t.exports=r},1957:(t,e,n)=>{var i="object"==typeof n.g&&n.g&&n.g.Object===Object&&n.g;t.exports=i},5050:(t,e,n)=>{var i=n(7019);t.exports=function(t,e){var n=t.__data__;return i(e)?n["string"==typeof e?"string":"hash"]:n.map}},852:(t,e,n)=>{var i=n(8458),r=n(7801);t.exports=function(t,e){var n=r(t,e);return i(n)?n:undefined}},5924:(t,e,n)=>{var i=n(5569)(Object.getPrototypeOf,Object);t.exports=i},9607:(t,e,n)=>{var i=n(2705),r=Object.prototype,a=r.hasOwnProperty,o=r.toString,s=i?i.toStringTag:undefined;t.exports=function(t){var e=a.call(t,s),n=t[s];try{t[s]=undefined;var i=!0}catch(l){}var r=o.call(t);return i&&(e?t[s]=n:delete t[s]),r}},7801:t=>{t.exports=function(t,e){return null==t?undefined:t[e]}},222:(t,e,n)=>{var i=n(1811),r=n(5694),a=n(1469),o=n(5776),s=n(1780),l=n(327);t.exports=function(t,e,n){for(var h=-1,u=(e=i(e,t)).length,c=!1;++h{var i=n(4536);t.exports=function(){this.__data__=i?i(null):{},this.size=0}},401:t=>{t.exports=function(t){var e=this.has(t)&&delete this.__data__[t];return this.size-=e?1:0,e}},7667:(t,e,n)=>{var i=n(4536),r=Object.prototype.hasOwnProperty;t.exports=function(t){var e=this.__data__;if(i){var n=e[t];return"__lodash_hash_undefined__"===n?undefined:n}return r.call(e,t)?e[t]:undefined}},1327:(t,e,n)=>{var i=n(4536),r=Object.prototype.hasOwnProperty;t.exports=function(t){var e=this.__data__;return i?e[t]!==undefined:r.call(e,t)}},1866:(t,e,n)=>{var i=n(4536);t.exports=function(t,e){var n=this.__data__;return this.size+=this.has(t)?0:1,n[t]=i&&e===undefined?"__lodash_hash_undefined__":e,this}},8517:(t,e,n)=>{var i=n(3118),r=n(5924),a=n(5726);t.exports=function(t){return"function"!=typeof t.constructor||a(t)?{}:i(r(t))}},5776:t=>{var e=/^(?:0|[1-9]\d*)$/;t.exports=function(t,n){var i=typeof t;return!!(n=null==n?9007199254740991:n)&&("number"==i||"symbol"!=i&&e.test(t))&&t>-1&&t%1==0&&t{var i=n(7813),r=n(8612),a=n(5776),o=n(3218);t.exports=function(t,e,n){if(!o(n))return!1;var s=typeof e;return!!("number"==s?r(n)&&a(e,n.length):"string"==s&&e in n)&&i(n[e],t)}},5403:(t,e,n)=>{var i=n(1469),r=n(3448),a=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,o=/^\w*$/;t.exports=function(t,e){if(i(t))return!1;var n=typeof t;return!("number"!=n&&"symbol"!=n&&"boolean"!=n&&null!=t&&!r(t))||(o.test(t)||!a.test(t)||null!=e&&t in Object(e))}},7019:t=>{t.exports=function(t){var e=typeof t;return"string"==e||"number"==e||"symbol"==e||"boolean"==e?"__proto__"!==t:null===t}},5346:(t,e,n)=>{var i,r=n(4429),a=(i=/[^.]+$/.exec(r&&r.keys&&r.keys.IE_PROTO||""))?"Symbol(src)_1."+i:"";t.exports=function(t){return!!a&&a in t}},5726:t=>{var e=Object.prototype;t.exports=function(t){var n=t&&t.constructor;return t===("function"==typeof n&&n.prototype||e)}},7040:t=>{t.exports=function(){this.__data__=[],this.size=0}},4125:(t,e,n)=>{var i=n(8470),r=Array.prototype.splice;t.exports=function(t){var e=this.__data__,n=i(e,t);return!(n<0)&&(n==e.length-1?e.pop():r.call(e,n,1),--this.size,!0)}},2117:(t,e,n)=>{var i=n(8470);t.exports=function(t){var e=this.__data__,n=i(e,t);return n<0?undefined:e[n][1]}},7518:(t,e,n)=>{var i=n(8470);t.exports=function(t){return i(this.__data__,t)>-1}},4705:(t,e,n)=>{var i=n(8470);t.exports=function(t,e){var n=this.__data__,r=i(n,t);return r<0?(++this.size,n.push([t,e])):n[r][1]=e,this}},4785:(t,e,n)=>{var i=n(1989),r=n(8407),a=n(7071);t.exports=function(){this.size=0,this.__data__={hash:new i,map:new(a||r),string:new i}}},1285:(t,e,n)=>{var i=n(5050);t.exports=function(t){var e=i(this,t)["delete"](t);return this.size-=e?1:0,e}},6e3:(t,e,n)=>{var i=n(5050);t.exports=function(t){return i(this,t).get(t)}},9916:(t,e,n)=>{var i=n(5050);t.exports=function(t){return i(this,t).has(t)}},5265:(t,e,n)=>{var i=n(5050);t.exports=function(t,e){var n=i(this,t),r=n.size;return n.set(t,e),this.size+=n.size==r?0:1,this}},4523:(t,e,n)=>{var i=n(8306);t.exports=function(t){var e=i(t,(function(t){return 500===n.size&&n.clear(),t})),n=e.cache;return e}},4536:(t,e,n)=>{var i=n(852)(Object,"create");t.exports=i},3498:t=>{t.exports=function(t){var e=[];if(null!=t)for(var n in Object(t))e.push(n);return e}},1167:(t,e,n)=>{t=n.nmd(t);var i=n(1957),r=e&&!e.nodeType&&e,a=r&&t&&!t.nodeType&&t,o=a&&a.exports===r&&i.process,s=function(){try{var t=a&&a.require&&a.require("util").types;return t||o&&o.binding&&o.binding("util")}catch(e){}}();t.exports=s},2333:t=>{var e=Object.prototype.toString;t.exports=function(t){return e.call(t)}},5569:t=>{t.exports=function(t,e){return function(n){return t(e(n))}}},5357:(t,e,n)=>{var i=n(6874),r=Math.max;t.exports=function(t,e,n){return e=r(e===undefined?t.length-1:e,0),function(){for(var a=arguments,o=-1,s=r(a.length-e,0),l=Array(s);++o{var i=n(1957),r="object"==typeof self&&self&&self.Object===Object&&self,a=i||r||Function("return this")();t.exports=a},6390:t=>{t.exports=function(t,e){if(("constructor"!==e||"function"!=typeof t[e])&&"__proto__"!=e)return t[e]}},61:(t,e,n)=>{var i=n(6560),r=n(1275)(i);t.exports=r},1275:t=>{var e=Date.now;t.exports=function(t){var n=0,i=0;return function(){var r=e(),a=16-(r-i);if(i=r,a>0){if(++n>=800)return arguments[0]}else n=0;return t.apply(undefined,arguments)}}},7465:(t,e,n)=>{var i=n(8407);t.exports=function(){this.__data__=new i,this.size=0}},3779:t=>{t.exports=function(t){var e=this.__data__,n=e["delete"](t);return this.size=e.size,n}},7599:t=>{t.exports=function(t){return this.__data__.get(t)}},4758:t=>{t.exports=function(t){return this.__data__.has(t)}},4309:(t,e,n)=>{var i=n(8407),r=n(7071),a=n(3369);t.exports=function(t,e){var n=this.__data__;if(n instanceof i){var o=n.__data__;if(!r||o.length<199)return o.push([t,e]),this.size=++n.size,this;n=this.__data__=new a(o)}return n.set(t,e),this.size=n.size,this}},5514:(t,e,n)=>{var i=n(4523),r=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,a=/\\(\\)?/g,o=i((function(t){var e=[];return 46===t.charCodeAt(0)&&e.push(""),t.replace(r,(function(t,n,i,r){e.push(i?r.replace(a,"$1"):n||t)})),e}));t.exports=o},327:(t,e,n)=>{var i=n(3448);t.exports=function(t){if("string"==typeof t||i(t))return t;var e=t+"";return"0"==e&&1/t==-Infinity?"-0":e}},346:t=>{var e=Function.prototype.toString;t.exports=function(t){if(null!=t){try{return e.call(t)}catch(n){}try{return t+""}catch(n){}}return""}},5703:t=>{t.exports=function(t){return function(){return t}}},7813:t=>{t.exports=function(t,e){return t===e||t!=t&&e!=e}},7361:(t,e,n)=>{var i=n(7786);t.exports=function(t,e,n){var r=null==t?undefined:i(t,e);return r===undefined?n:r}},8721:(t,e,n)=>{var i=n(8565),r=n(222);t.exports=function(t,e){return null!=t&&r(t,e,i)}},6557:t=>{t.exports=function(t){return t}},5694:(t,e,n)=>{var i=n(9454),r=n(7005),a=Object.prototype,o=a.hasOwnProperty,s=a.propertyIsEnumerable,l=i(function(){return arguments}())?i:function(t){return r(t)&&o.call(t,"callee")&&!s.call(t,"callee")};t.exports=l},1469:t=>{var e=Array.isArray;t.exports=e},8612:(t,e,n)=>{var i=n(3560),r=n(1780);t.exports=function(t){return null!=t&&r(t.length)&&!i(t)}},9246:(t,e,n)=>{var i=n(8612),r=n(7005);t.exports=function(t){return r(t)&&i(t)}},4144:(t,e,n)=>{t=n.nmd(t);var i=n(5639),r=n(5062),a=e&&!e.nodeType&&e,o=a&&t&&!t.nodeType&&t,s=o&&o.exports===a?i.Buffer:undefined,l=(s?s.isBuffer:undefined)||r;t.exports=l},3560:(t,e,n)=>{var i=n(4239),r=n(3218);t.exports=function(t){if(!r(t))return!1;var e=i(t);return"[object Function]"==e||"[object GeneratorFunction]"==e||"[object AsyncFunction]"==e||"[object Proxy]"==e}},1780:t=>{t.exports=function(t){return"number"==typeof t&&t>-1&&t%1==0&&t<=9007199254740991}},3218:t=>{t.exports=function(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}},7005:t=>{t.exports=function(t){return null!=t&&"object"==typeof t}},8630:(t,e,n)=>{var i=n(4239),r=n(5924),a=n(7005),o=Function.prototype,s=Object.prototype,l=o.toString,h=s.hasOwnProperty,u=l.call(Object);t.exports=function(t){if(!a(t)||"[object Object]"!=i(t))return!1;var e=r(t);if(null===e)return!0;var n=h.call(e,"constructor")&&e.constructor;return"function"==typeof n&&n instanceof n&&l.call(n)==u}},3448:(t,e,n)=>{var i=n(4239),r=n(7005);t.exports=function(t){return"symbol"==typeof t||r(t)&&"[object Symbol]"==i(t)}},6719:(t,e,n)=>{var i=n(8749),r=n(1717),a=n(1167),o=a&&a.isTypedArray,s=o?r(o):i;t.exports=s},1704:(t,e,n)=>{var i=n(4636),r=n(313),a=n(8612);t.exports=function(t){return a(t)?i(t,!0):r(t)}},8306:(t,e,n)=>{var i=n(3369);function r(t,e){if("function"!=typeof t||null!=e&&"function"!=typeof e)throw new TypeError("Expected a function");var n=function(){var i=arguments,r=e?e.apply(this,i):i[0],a=n.cache;if(a.has(r))return a.get(r);var o=t.apply(this,i);return n.cache=a.set(r,o)||a,o};return n.cache=new(r.Cache||i),n}r.Cache=i,t.exports=r},2492:(t,e,n)=>{var i=n(2980),r=n(1463)((function(t,e,n){i(t,e,n)}));t.exports=r},5062:t=>{t.exports=function(){return!1}},9881:(t,e,n)=>{var i=n(8363),r=n(1704);t.exports=function(t){return i(t,r(t))}},9833:(t,e,n)=>{var i=n(531);t.exports=function(t){return null==t?"":i(t)}},2676:function(t){t.exports=function(){"use strict";function t(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function e(t,e){for(var n=0;ne?1:t0))break;if(null===e.right)break;if(n(t,e.right.key)>0&&(l=e.right,e.right=l.left,l.left=e,null===(e=l).right))break;a.right=e,a=e,e=e.right}}return a.right=e.left,o.left=e.right,e.left=r.right,e.right=r.left,e}function o(t,e,n,r){var o=new i(t,e);if(null===n)return o.left=o.right=null,o;var s=r(t,(n=a(t,n,r)).key);return s<0?(o.left=n.left,o.right=n,n.left=null):s>=0&&(o.right=n.right,o.left=n,n.right=null),o}function s(t,e,n){var i=null,r=null;if(e){var o=n((e=a(t,e,n)).key,t);0===o?(i=e.left,r=e.right):o<0?(r=e.right,e.right=null,i=e):(i=e.left,e.left=null,r=e)}return{left:i,right:r}}function l(t,e,n){return null===e?t:(null===t||((e=a(t.key,e,n)).left=t),e)}function h(t,e,n,i,r){if(t){i(e+(n?"└── ":"├── ")+r(t)+"\n");var a=e+(n?" ":"│ ");t.left&&h(t.left,a,!1,i,r),t.right&&h(t.right,a,!0,i,r)}}var u=function(){function t(t){void 0===t&&(t=r),this._root=null,this._size=0,this._comparator=t}return t.prototype.insert=function(t,e){return this._size++,this._root=o(t,e,this._root,this._comparator)},t.prototype.add=function(t,e){var n=new i(t,e);null===this._root&&(n.left=n.right=null,this._size++,this._root=n);var r=this._comparator,o=a(t,this._root,r),s=r(t,o.key);return 0===s?this._root=o:(s<0?(n.left=o.left,n.right=o,o.left=null):s>0&&(n.right=o.right,n.left=o,o.right=null),this._size++,this._root=n),this._root},t.prototype.remove=function(t){this._root=this._remove(t,this._root,this._comparator)},t.prototype._remove=function(t,e,n){var i;return null===e?null:0===n(t,(e=a(t,e,n)).key)?(null===e.left?i=e.right:(i=a(t,e.left,n)).right=e.right,this._size--,i):e},t.prototype.pop=function(){var t=this._root;if(t){for(;t.left;)t=t.left;return this._root=a(t.key,this._root,this._comparator),this._root=this._remove(t.key,this._root,this._comparator),{key:t.key,data:t.data}}return null},t.prototype.findStatic=function(t){for(var e=this._root,n=this._comparator;e;){var i=n(t,e.key);if(0===i)return e;e=i<0?e.left:e.right}return null},t.prototype.find=function(t){return this._root&&(this._root=a(t,this._root,this._comparator),0!==this._comparator(t,this._root.key))?null:this._root},t.prototype.contains=function(t){for(var e=this._root,n=this._comparator;e;){var i=n(t,e.key);if(0===i)return!0;e=i<0?e.left:e.right}return!1},t.prototype.forEach=function(t,e){for(var n=this._root,i=[],r=!1;!r;)null!==n?(i.push(n),n=n.left):0!==i.length?(n=i.pop(),t.call(e,n),n=n.right):r=!0;return this},t.prototype.range=function(t,e,n,i){for(var r=[],a=this._comparator,o=this._root;0!==r.length||o;)if(o)r.push(o),o=o.left;else{if(a((o=r.pop()).key,e)>0)break;if(a(o.key,t)>=0&&n.call(i,o))return this;o=o.right}return this},t.prototype.keys=function(){var t=[];return this.forEach((function(e){var n=e.key;return t.push(n)})),t},t.prototype.values=function(){var t=[];return this.forEach((function(e){var n=e.data;return t.push(n)})),t},t.prototype.min=function(){return this._root?this.minNode(this._root).key:null},t.prototype.max=function(){return this._root?this.maxNode(this._root).key:null},t.prototype.minNode=function(t){if(void 0===t&&(t=this._root),t)for(;t.left;)t=t.left;return t},t.prototype.maxNode=function(t){if(void 0===t&&(t=this._root),t)for(;t.right;)t=t.right;return t},t.prototype.at=function(t){for(var e=this._root,n=!1,i=0,r=[];!n;)if(e)r.push(e),e=e.left;else if(r.length>0){if(e=r.pop(),i===t)return e;i++,e=e.right}else n=!0;return null},t.prototype.next=function(t){var e=this._root,n=null;if(t.right){for(n=t.right;n.left;)n=n.left;return n}for(var i=this._comparator;e;){var r=i(t.key,e.key);if(0===r)break;r<0?(n=e,e=e.left):e=e.right}return n},t.prototype.prev=function(t){var e=this._root,n=null;if(null!==t.left){for(n=t.left;n.right;)n=n.right;return n}for(var i=this._comparator;e;){var r=i(t.key,e.key);if(0===r)break;r<0?e=e.left:(n=e,e=e.right)}return n},t.prototype.clear=function(){return this._root=null,this._size=0,this},t.prototype.toList=function(){return d(this._root)},t.prototype.load=function(t,e,n){void 0===e&&(e=[]),void 0===n&&(n=!1);var i=t.length,r=this._comparator;if(n&&_(t,e,0,i-1,r),null===this._root)this._root=c(t,e,0,i),this._size=i;else{var a=g(this.toList(),p(t,e),r);i=this._size+i,this._root=f({head:a},0,i)}return this},t.prototype.isEmpty=function(){return null===this._root},Object.defineProperty(t.prototype,"size",{get:function(){return this._size},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"root",{get:function(){return this._root},enumerable:!0,configurable:!0}),t.prototype.toString=function(t){void 0===t&&(t=function(t){return String(t.key)});var e=[];return h(this._root,"",!0,(function(t){return e.push(t)}),t),e.join("")},t.prototype.update=function(t,e,n){var i=this._comparator,r=s(t,this._root,i),a=r.left,h=r.right;i(t,e)<0?h=o(e,n,h,i):a=o(e,n,a,i),this._root=l(a,h,i)},t.prototype.split=function(t){return s(t,this._root,this._comparator)},t}();function c(t,e,n,r){var a=r-n;if(a>0){var o=n+Math.floor(a/2),s=t[o],l=e[o],h=new i(s,l);return h.left=c(t,e,n,o),h.right=c(t,e,o+1,r),h}return null}function p(t,e){for(var n=new i(null,null),r=n,a=0;a0?e=(e=o=o.next=n.pop()).right:r=!0;return o.next=null,a.next}function f(t,e,n){var i=n-e;if(i>0){var r=e+Math.floor(i/2),a=f(t,e,r),o=t.head;return o.left=a,t.head=t.head.next,o.right=f(t,r+1,n),o}return null}function g(t,e,n){for(var r=new i(null,null),a=r,o=t,s=e;null!==o&&null!==s;)n(o.key,s.key)<0?(a.next=o,o=o.next):(a.next=s,s=s.next),a=a.next;return null!==o?a.next=o:null!==s&&(a.next=s),r.next}function _(t,e,n,i,r){if(!(n>=i)){for(var a=t[n+i>>1],o=n-1,s=i+1;;){do{o++}while(r(t[o],a)<0);do{s--}while(r(t[s],a)>0);if(o>=s)break;var l=t[o];t[o]=t[s],t[s]=l,l=e[o],e[o]=e[s],e[s]=l}_(t,e,n,s,r),_(t,e,s+1,i,r)}}var m=function(t,e){return t.ll.x<=e.x&&e.x<=t.ur.x&&t.ll.y<=e.y&&e.y<=t.ur.y},y=function(t,e){if(e.ur.xe.x?1:t.ye.y?1:0}}]),n(e,[{key:"link",value:function(t){if(t.point===this.point)throw new Error("Tried to link already linked events");for(var e=t.point.events,n=0,i=e.length;n=0&&l>=0?oh?-1:0:a<0&&l<0?oh?1:0:la?1:0}}}]),e}(),I=0,j=function(){function e(n,i,r,a){t(this,e),this.id=++I,this.leftSE=n,n.segment=this,n.otherSE=i,this.rightSE=i,i.segment=this,i.otherSE=n,this.rings=r,this.windings=a}return n(e,null,[{key:"compare",value:function(t,e){var n=t.leftSE.point.x,i=e.leftSE.point.x,r=t.rightSE.point.x,a=e.rightSE.point.x;if(ao&&s>l)return-1;var u=t.comparePoint(e.leftSE.point);if(u<0)return 1;if(u>0)return-1;var c=e.comparePoint(t.rightSE.point);return 0!==c?c:-1}if(n>i){if(os&&o>h)return 1;var p=e.comparePoint(t.leftSE.point);if(0!==p)return p;var d=t.comparePoint(e.rightSE.point);return d<0?1:d>0?-1:1}if(os)return 1;if(ra){var g=t.comparePoint(e.rightSE.point);if(g<0)return 1;if(g>0)return-1}if(r!==a){var _=l-o,m=r-n,y=h-s,v=a-i;if(_>m&&yv)return-1}return r>a?1:rh?1:t.ide.id?1:0}}]),n(e,[{key:"replaceRightSE",value:function(t){this.rightSE=t,this.rightSE.segment=this,this.rightSE.otherSE=this.leftSE,this.leftSE.otherSE=this.rightSE}},{key:"bbox",value:function(){var t=this.leftSE.point.y,e=this.rightSE.point.y;return{ll:{x:this.leftSE.point.x,y:te?t:e}}}},{key:"vector",value:function(){return{x:this.rightSE.point.x-this.leftSE.point.x,y:this.rightSE.point.y-this.leftSE.point.y}}},{key:"isAnEndpoint",value:function(t){return t.x===this.leftSE.point.x&&t.y===this.leftSE.point.y||t.x===this.rightSE.point.x&&t.y===this.rightSE.point.y}},{key:"comparePoint",value:function(t){if(this.isAnEndpoint(t))return 0;var e=this.leftSE.point,n=this.rightSE.point,i=this.vector();if(e.x===n.x)return t.x===e.x?0:t.x0&&s.swapEvents(),T.comparePoints(this.leftSE.point,this.rightSE.point)>0&&this.swapEvents(),i&&(r.checkForConsuming(),a.checkForConsuming()),n}},{key:"swapEvents",value:function(){var t=this.rightSE;this.rightSE=this.leftSE,this.leftSE=t,this.leftSE.isLeft=!0,this.rightSE.isLeft=!1;for(var e=0,n=this.windings.length;e0){var a=n;n=i,i=a}if(n.prev===i){var o=n;n=i,i=o}for(var s=0,l=i.rings.length;s0))throw new Error("Tried to create degenerate segment at [".concat(t.x,", ").concat(t.y,"]"));r=n,a=t,o=-1}return new e(new T(r,!0),new T(a,!1),[i],[o])}}]),e}(),G=function(){function e(n,i,r){if(t(this,e),!Array.isArray(n)||0===n.length)throw new Error("Input geometry is not a valid Polygon or MultiPolygon");if(this.poly=i,this.isExterior=r,this.segments=[],"number"!=typeof n[0][0]||"number"!=typeof n[0][1])throw new Error("Input geometry is not a valid Polygon or MultiPolygon");var a=x.round(n[0][0],n[0][1]);this.bbox={ll:{x:a.x,y:a.y},ur:{x:a.x,y:a.y}};for(var o=a,s=1,l=n.length;sthis.bbox.ur.x&&(this.bbox.ur.x=h.x),h.y>this.bbox.ur.y&&(this.bbox.ur.y=h.y),o=h)}a.x===o.x&&a.y===o.y||this.segments.push(j.fromRing(o,a,this))}return n(e,[{key:"getSweepEvents",value:function(){for(var t=[],e=0,n=this.segments.length;ethis.bbox.ur.x&&(this.bbox.ur.x=o.bbox.ur.x),o.bbox.ur.y>this.bbox.ur.y&&(this.bbox.ur.y=o.bbox.ur.y),this.interiorRings.push(o)}this.multiPoly=i}return n(e,[{key:"getSweepEvents",value:function(){for(var t=this.exteriorRing.getSweepEvents(),e=0,n=this.interiorRings.length;ethis.bbox.ur.x&&(this.bbox.ur.x=o.bbox.ur.x),o.bbox.ur.y>this.bbox.ur.y&&(this.bbox.ur.y=o.bbox.ur.y),this.polys.push(o)}this.isSubject=i}return n(e,[{key:"getSweepEvents",value:function(){for(var t=[],e=0,n=this.polys.length;e0&&(t=i)}for(var r=t.segment.prevInResult(),a=r?r.prevInResult():null;;){if(!r)return null;if(!a)return r.ringOut;if(a.ringOut!==r.ringOut)return a.ringOut.enclosingRing()!==r.ringOut?r.ringOut:r.ringOut.enclosingRing();r=a.prevInResult(),a=r?r.prevInResult():null}}}]),e}(),U=function(){function e(n){t(this,e),this.exteriorRing=n,n.poly=this,this.interiorRings=[]}return n(e,[{key:"addInterior",value:function(t){this.interiorRings.push(t),t.poly=this}},{key:"getGeom",value:function(){var t=[this.exteriorRing.getGeom()];if(null===t[0])return null;for(var e=0,n=this.interiorRings.length;e1&&arguments[1]!==undefined?arguments[1]:j.compare;t(this,e),this.queue=n,this.tree=new u(i),this.segments=[]}return n(e,[{key:"process",value:function(t){var e=t.segment,n=[];if(t.consumedBy)return t.isLeft?this.queue.remove(t.otherSE):this.tree.remove(e),n;var i=t.isLeft?this.tree.insert(e):this.tree.find(e);if(!i)throw new Error("Unable to find segment #".concat(e.id," ")+"[".concat(e.leftSE.point.x,", ").concat(e.leftSE.point.y,"] -> ")+"[".concat(e.rightSE.point.x,", ").concat(e.rightSE.point.y,"] ")+"in SweepLine tree. Please submit a bug report.");for(var r=i,a=i,o=undefined,s=undefined;o===undefined;)null===(r=this.tree.prev(r))?o=null:r.key.consumedBy===undefined&&(o=r.key);for(;s===undefined;)null===(a=this.tree.next(a))?s=null:a.key.consumedBy===undefined&&(s=a.key);if(t.isLeft){var l=null;if(o){var h=o.getIntersection(e);if(null!==h&&(e.isAnEndpoint(h)||(l=h),!o.isAnEndpoint(h)))for(var u=this._splitSafely(o,h),c=0,p=u.length;c0?(this.tree.remove(e),n.push(t)):(this.segments.push(e),e.prev=o)}else{if(o&&s){var k=o.getIntersection(s);if(null!==k){if(!o.isAnEndpoint(k))for(var M=this._splitSafely(o,k),x=0,w=M.length;xK)throw new Error("Infinite loop when putting segment endpoints in a priority queue (queue size too big). Please file a bug report.");for(var b=new F(f),k=f.size,M=f.pop();M;){var w=M.key;if(f.size===k){var C=w.segment;throw new Error("Unable to pop() ".concat(w.isLeft?"left":"right"," SweepEvent ")+"[".concat(w.point.x,", ").concat(w.point.y,"] from segment #").concat(C.id," ")+"[".concat(C.leftSE.point.x,", ").concat(C.leftSE.point.y,"] -> ")+"[".concat(C.rightSE.point.x,", ").concat(C.rightSE.point.y,"] from queue. ")+"Please file a bug report.")}if(f.size>K)throw new Error("Infinite loop when passing sweep line over endpoints (queue size too big). Please file a bug report.");if(b.segments.length>H)throw new Error("Infinite loop when passing sweep line over endpoints (too many sweep line segments). Please file a bug report.");for(var P=b.process(w),E=0,S=P.length;E1?e-1:0),i=1;i1?e-1:0),i=1;i1?e-1:0),i=1;i1?e-1:0),i=1;ii;){if(r-i>600){var o=r-i+1,l=n-i+1,h=Math.log(o),u=.5*Math.exp(2*h/3),c=.5*Math.sqrt(h*u*(o-u)/o)*(l-o/2<0?-1:1);s(t,n,Math.max(i,Math.floor(n-l*u/o+c)),Math.min(r,Math.floor(n+(o-l)*u/o+c)),a)}var p=t[n],d=i,f=r;for(e(t,i,n),a(t[r],p)>0&&e(t,i,r);d0;)f--}0===a(t[i],p)?e(t,i,f):e(t,++f,r),f<=n&&(i=f+1),n<=f&&(r=f-1)}}(t,i,r||0,a||t.length-1,o||n)}function e(t,e,n){var i=t[e];t[e]=t[n],t[n]=i}function n(t,e){return te?1:0}var i=function(t){void 0===t&&(t=9),this._maxEntries=Math.max(4,t),this._minEntries=Math.max(2,Math.ceil(.4*this._maxEntries)),this.clear()};function r(t,e,n){if(!n)return e.indexOf(t);for(var i=0;i=t.minX&&e.maxY>=t.minY}function f(t){return{children:t,height:1,leaf:!0,minX:1/0,minY:1/0,maxX:-1/0,maxY:-1/0}}function g(e,n,i,r,a){for(var o=[n,i];o.length;)if(!((i=o.pop())-(n=o.pop())<=r)){var s=n+Math.ceil((i-n)/r/2)*r;t(e,s,n,i,a),o.push(n,s,s,i)}}return i.prototype.all=function(){return this._all(this.data,[])},i.prototype.search=function(t){var e=this.data,n=[];if(!d(t,e))return n;for(var i=this.toBBox,r=[];e;){for(var a=0;a=0&&r[e].children.length>this._maxEntries;)this._split(r,e),e--;this._adjustParentBBoxes(i,r,e)},i.prototype._split=function(t,e){var n=t[e],i=n.children.length,r=this._minEntries;this._chooseSplitAxis(n,r,i);var o=this._chooseSplitIndex(n,r,i),s=f(n.children.splice(o,n.children.length-o));s.height=n.height,s.leaf=n.leaf,a(n,this.toBBox),a(s,this.toBBox),e?t[e-1].children.push(s):this._splitRoot(n,s)},i.prototype._splitRoot=function(t,e){this.data=f([t,e]),this.data.height=t.height+1,this.data.leaf=!1,a(this.data,this.toBBox)},i.prototype._chooseSplitIndex=function(t,e,n){for(var i,r,a,s,l,h,c,p=1/0,d=1/0,f=e;f<=n-e;f++){var g=o(t,0,f,this.toBBox),_=o(t,f,n,this.toBBox),m=(r=g,a=_,s=void 0,l=void 0,h=void 0,c=void 0,s=Math.max(r.minX,a.minX),l=Math.max(r.minY,a.minY),h=Math.min(r.maxX,a.maxX),c=Math.min(r.maxY,a.maxY),Math.max(0,h-s)*Math.max(0,c-l)),y=u(g)+u(_);m=e;d--){var f=t.children[d];s(l,t.leaf?r(f):f),h+=c(l)}return h},i.prototype._adjustParentBBoxes=function(t,e,n){for(var i=n;i>=0;i--)s(e[i],t)},i.prototype._condense=function(t){for(var e=t.length-1,n=void 0;e>=0;e--)0===t[e].children.length?e>0?(n=t[e-1].children).splice(n.indexOf(t[e]),1):this.clear():a(t[e],this.toBBox)},i}()}},e={};function n(i){var r=e[i];if(r!==undefined)return r.exports;var a=e[i]={id:i,loaded:!1,exports:{}};return t[i].call(a.exports,a,a.exports,n),a.loaded=!0,a.exports}n.n=t=>{var e=t&&t.__esModule?()=>t["default"]:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var i in e)n.o(e,i)&&!n.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),n.nmd=t=>(t.paths=[],t.children||(t.children=[]),t);n(1052)})(); -\ No newline at end of file -+(()=>{var t={9705:(t,e,n)=>{"use strict";var i=n(1540);function r(t){var e=[Infinity,Infinity,-Infinity,-Infinity];return i.coordEach(t,(function(t){e[0]>t[0]&&(e[0]=t[0]),e[1]>t[1]&&(e[1]=t[1]),e[2]{"use strict";function n(t,e,n){void 0===n&&(n={});var i={type:"Feature"};return(0===n.id||n.id)&&(i.id=n.id),n.bbox&&(i.bbox=n.bbox),i.properties=e||{},i.geometry=t,i}function i(t,e,i){if(void 0===i&&(i={}),!t)throw new Error("coordinates is required");if(!Array.isArray(t))throw new Error("coordinates must be an Array");if(t.length<2)throw new Error("coordinates must be at least 2 numbers long");if(!d(t[0])||!d(t[1]))throw new Error("coordinates must contain numbers");return n({type:"Point",coordinates:t},e,i)}function r(t,e,i){void 0===i&&(i={});for(var r=0,a=t;r=0))throw new Error("precision must be a positive number");var n=Math.pow(10,e||0);return Math.round(t*n)/n},e.radiansToLength=u,e.lengthToRadians=c,e.lengthToDegrees=function(t,e){return p(c(t,e))},e.bearingToAzimuth=function(t){var e=t%360;return e<0&&(e+=360),e},e.radiansToDegrees=p,e.degreesToRadians=function(t){return t%360*Math.PI/180},e.convertLength=function(t,e,n){if(void 0===e&&(e="kilometers"),void 0===n&&(n="kilometers"),!(t>=0))throw new Error("length must be a positive number");return u(c(t,e),n)},e.convertArea=function(t,n,i){if(void 0===n&&(n="meters"),void 0===i&&(i="kilometers"),!(t>=0))throw new Error("area must be a positive number");var r=e.areaFactors[n];if(!r)throw new Error("invalid original units");var a=e.areaFactors[i];if(!a)throw new Error("invalid final units");return t/r*a},e.isNumber=d,e.isObject=function(t){return!!t&&t.constructor===Object},e.validateBBox=function(t){if(!t)throw new Error("bbox is required");if(!Array.isArray(t))throw new Error("bbox must be an Array");if(4!==t.length&&6!==t.length)throw new Error("bbox must be an Array of 4 or 6 numbers");t.forEach((function(t){if(!d(t))throw new Error("bbox must only contain numbers")}))},e.validateId=function(t){if(!t)throw new Error("id is required");if(-1===["string","number"].indexOf(typeof t))throw new Error("id must be a number or a string")}},1540:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0});var i=n(4102);function r(t,e,n){if(null!==t)for(var i,a,o,s,l,h,u,c,p=0,d=0,f=t.type,g="FeatureCollection"===f,_="Feature"===f,m=g?t.features.length:1,y=0;yh||d>u||f>c)return l=r,h=n,u=d,c=f,void(o=0);var g=i.lineString([l,r],t.properties);if(!1===e(g,n,a,f,o))return!1;o++,l=r}))&&void 0}}}))}function u(t,e){if(!t)throw new Error("geojson is required");l(t,(function(t,n,r){if(null!==t.geometry){var a=t.geometry.type,o=t.geometry.coordinates;switch(a){case"LineString":if(!1===e(t,n,r,0,0))return!1;break;case"Polygon":for(var s=0;s{"use strict";n(7107);var i=n(2492),r=n.n(i);const a=JSON.parse('{"tooltips":{"placeMarker":"Click to place marker","firstVertex":"Click to place first vertex","continueLine":"Click to continue drawing","finishLine":"Click any existing marker to finish","finishPoly":"Click first marker to finish","finishRect":"Click to finish","startCircle":"Click to place circle center","finishCircle":"Click to finish circle","placeCircleMarker":"Click to place circle marker"},"actions":{"finish":"Finish","cancel":"Cancel","removeLastVertex":"Remove Last Vertex"},"buttonTitles":{"drawMarkerButton":"Draw Marker","drawPolyButton":"Draw Polygons","drawLineButton":"Draw Polyline","drawCircleButton":"Draw Circle","drawRectButton":"Draw Rectangle","editButton":"Edit Layers","dragButton":"Drag Layers","cutButton":"Cut Layers","deleteButton":"Remove Layers","drawCircleMarkerButton":"Draw Circle Marker","snappingButton":"Snap dragged marker to other layers and vertices","pinningButton":"Pin shared vertices together","rotateButton":"Rotate Layers"}}'),o=JSON.parse('{"tooltips":{"placeMarker":"Platziere den Marker mit Klick","firstVertex":"Platziere den ersten Marker mit Klick","continueLine":"Klicke, um weiter zu zeichnen","finishLine":"Beende mit Klick auf existierenden Marker","finishPoly":"Beende mit Klick auf ersten Marker","finishRect":"Beende mit Klick","startCircle":"Platziere das Kreiszentrum mit Klick","finishCircle":"Beende den Kreis mit Klick","placeCircleMarker":"Platziere den Kreismarker mit Klick"},"actions":{"finish":"Beenden","cancel":"Abbrechen","removeLastVertex":"Letzten Vertex löschen"},"buttonTitles":{"drawMarkerButton":"Marker zeichnen","drawPolyButton":"Polygon zeichnen","drawLineButton":"Polyline zeichnen","drawCircleButton":"Kreis zeichnen","drawRectButton":"Rechteck zeichnen","editButton":"Layer editieren","dragButton":"Layer bewegen","cutButton":"Layer schneiden","deleteButton":"Layer löschen","drawCircleMarkerButton":"Kreismarker zeichnen","snappingButton":"Bewegter Layer an andere Layer oder Vertexe einhacken","pinningButton":"Vertexe an der gleichen Position verknüpfen","rotateButton":"Layer drehen"}}'),s=JSON.parse('{"tooltips":{"placeMarker":"Clicca per posizionare un Marker","firstVertex":"Clicca per posizionare il primo vertice","continueLine":"Clicca per continuare a disegnare","finishLine":"Clicca qualsiasi marker esistente per terminare","finishPoly":"Clicca il primo marker per terminare","finishRect":"Clicca per terminare","startCircle":"Clicca per posizionare il punto centrale del cerchio","finishCircle":"Clicca per terminare il cerchio","placeCircleMarker":"Clicca per posizionare un Marker del cherchio"},"actions":{"finish":"Termina","cancel":"Annulla","removeLastVertex":"Rimuovi l\'ultimo vertice"},"buttonTitles":{"drawMarkerButton":"Disegna Marker","drawPolyButton":"Disegna Poligoni","drawLineButton":"Disegna Polilinea","drawCircleButton":"Disegna Cerchio","drawRectButton":"Disegna Rettangolo","editButton":"Modifica Livelli","dragButton":"Sposta Livelli","cutButton":"Ritaglia Livelli","deleteButton":"Elimina Livelli","drawCircleMarkerButton":"Disegna Marker del Cerchio","snappingButton":"Snap ha trascinato il pennarello su altri strati e vertici","pinningButton":"Pin condiviso vertici insieme"}}'),l=JSON.parse('{"tooltips":{"placeMarker":"Klik untuk menempatkan marker","firstVertex":"Klik untuk menempatkan vertex pertama","continueLine":"Klik untuk meneruskan digitasi","finishLine":"Klik pada sembarang marker yang ada untuk mengakhiri","finishPoly":"Klik marker pertama untuk mengakhiri","finishRect":"Klik untuk mengakhiri","startCircle":"Klik untuk menempatkan titik pusat lingkaran","finishCircle":"Klik untuk mengakhiri lingkaran","placeCircleMarker":"Klik untuk menempatkan penanda lingkarann"},"actions":{"finish":"Selesai","cancel":"Batal","removeLastVertex":"Hilangkan Vertex Terakhir"},"buttonTitles":{"drawMarkerButton":"Digitasi Marker","drawPolyButton":"Digitasi Polygon","drawLineButton":"Digitasi Polyline","drawCircleButton":"Digitasi Lingkaran","drawRectButton":"Digitasi Segi Empat","editButton":"Edit Layer","dragButton":"Geser Layer","cutButton":"Potong Layer","deleteButton":"Hilangkan Layer","drawCircleMarkerButton":"Digitasi Penanda Lingkaran","snappingButton":"Jepretkan penanda yang ditarik ke lapisan dan simpul lain","pinningButton":"Sematkan simpul bersama bersama"}}'),h=JSON.parse('{"tooltips":{"placeMarker":"Adaugă un punct","firstVertex":"Apasă aici pentru a adăuga primul Vertex","continueLine":"Apasă aici pentru a continua desenul","finishLine":"Apasă pe orice obiect pentru a finisa desenul","finishPoly":"Apasă pe primul obiect pentru a finisa","finishRect":"Apasă pentru a finisa","startCircle":"Apasă pentru a desena un cerc","finishCircle":"Apasă pentru a finisa un cerc","placeCircleMarker":"Adaugă un punct"},"actions":{"finish":"Termină","cancel":"Anulează","removeLastVertex":"Șterge ultimul Vertex"},"buttonTitles":{"drawMarkerButton":"Adaugă o bulină","drawPolyButton":"Desenează un poligon","drawLineButton":"Desenează o linie","drawCircleButton":"Desenează un cerc","drawRectButton":"Desenează un dreptunghi","editButton":"Editează straturile","dragButton":"Mută straturile","cutButton":"Taie straturile","deleteButton":"Șterge straturile","drawCircleMarkerButton":"Desenează marcatorul cercului","snappingButton":"Fixați marcatorul glisat pe alte straturi și vârfuri","pinningButton":"Fixați vârfurile partajate împreună"}}'),u=JSON.parse('{"tooltips":{"placeMarker":"Нажмите, чтобы нанести маркер","firstVertex":"Нажмите, чтобы нанести первый объект","continueLine":"Нажмите, чтобы продолжить рисование","finishLine":"Нажмите любой существующий маркер для завершения","finishPoly":"Выберите первую точку, чтобы закончить","finishRect":"Нажмите, чтобы закончить","startCircle":"Нажмите, чтобы добавить центр круга","finishCircle":"Нажмите, чтобы задать радиус","placeCircleMarker":"Нажмите, чтобы нанести круговой маркер"},"actions":{"finish":"Завершить","cancel":"Отменить","removeLastVertex":"Отменить последнее действие"},"buttonTitles":{"drawMarkerButton":"Добавить маркер","drawPolyButton":"Рисовать полигон","drawLineButton":"Рисовать кривую","drawCircleButton":"Рисовать круг","drawRectButton":"Рисовать прямоугольник","editButton":"Редактировать слой","dragButton":"Перенести слой","cutButton":"Вырезать слой","deleteButton":"Удалить слой","drawCircleMarkerButton":"Добавить круговой маркер","snappingButton":"Привязать перетаскиваемый маркер к другим слоям и вершинам","pinningButton":"Связать общие точки вместе"}}'),c=JSON.parse('{"tooltips":{"placeMarker":"Presiona para colocar un marcador","firstVertex":"Presiona para colocar el primer vértice","continueLine":"Presiona para continuar dibujando","finishLine":"Presiona cualquier marcador existente para finalizar","finishPoly":"Presiona el primer marcador para finalizar","finishRect":"Presiona para finalizar","startCircle":"Presiona para colocar el centro del circulo","finishCircle":"Presiona para finalizar el circulo","placeCircleMarker":"Presiona para colocar un marcador de circulo"},"actions":{"finish":"Finalizar","cancel":"Cancelar","removeLastVertex":"Remover ultimo vértice"},"buttonTitles":{"drawMarkerButton":"Dibujar Marcador","drawPolyButton":"Dibujar Polígono","drawLineButton":"Dibujar Línea","drawCircleButton":"Dibujar Circulo","drawRectButton":"Dibujar Rectángulo","editButton":"Editar Capas","dragButton":"Arrastrar Capas","cutButton":"Cortar Capas","deleteButton":"Remover Capas","drawCircleMarkerButton":"Dibujar Marcador de Circulo","snappingButton":"El marcador de Snap arrastrado a otras capas y vértices","pinningButton":"Fijar juntos los vértices compartidos"}}'),p=JSON.parse('{"tooltips":{"placeMarker":"Klik om een marker te plaatsen","firstVertex":"Klik om het eerste punt te plaatsen","continueLine":"Klik om te blijven tekenen","finishLine":"Klik op een bestaand punt om te beëindigen","finishPoly":"Klik op het eerst punt om te beëindigen","finishRect":"Klik om te beëindigen","startCircle":"Klik om het middelpunt te plaatsen","finishCircle":"Klik om de cirkel te beëindigen","placeCircleMarker":"Klik om een marker te plaatsen"},"actions":{"finish":"Bewaar","cancel":"Annuleer","removeLastVertex":"Verwijder laatste punt"},"buttonTitles":{"drawMarkerButton":"Plaats Marker","drawPolyButton":"Teken een vlak","drawLineButton":"Teken een lijn","drawCircleButton":"Teken een cirkel","drawRectButton":"Teken een vierkant","editButton":"Bewerk","dragButton":"Verplaats","cutButton":"Knip","deleteButton":"Verwijder","drawCircleMarkerButton":"Plaats Marker","snappingButton":"Snap gesleepte marker naar andere lagen en hoekpunten","pinningButton":"Speld gedeelde hoekpunten samen"}}'),d=JSON.parse('{"tooltips":{"placeMarker":"Cliquez pour placer un marqueur","firstVertex":"Cliquez pour placer le premier sommet","continueLine":"Cliquez pour continuer à dessiner","finishLine":"Cliquez sur n\'importe quel marqueur pour terminer","finishPoly":"Cliquez sur le premier marqueur pour terminer","finishRect":"Cliquez pour terminer","startCircle":"Cliquez pour placer le centre du cercle","finishCircle":"Cliquez pour finir le cercle","placeCircleMarker":"Cliquez pour placer le marqueur circulaire"},"actions":{"finish":"Terminer","cancel":"Annuler","removeLastVertex":"Retirer le dernier sommet"},"buttonTitles":{"drawMarkerButton":"Placer des marqueurs","drawPolyButton":"Dessiner des polygones","drawLineButton":"Dessiner des polylignes","drawCircleButton":"Dessiner un cercle","drawRectButton":"Dessiner un rectangle","editButton":"Éditer des calques","dragButton":"Déplacer des calques","cutButton":"Couper des calques","deleteButton":"Supprimer des calques","drawCircleMarkerButton":"Dessiner un marqueur circulaire","snappingButton":"Glisser le marqueur vers d\'autres couches et sommets","pinningButton":"Épingler ensemble les sommets partagés","rotateButton":"Tourner des calques"}}'),f=JSON.parse('{"tooltips":{"placeMarker":"单击放置标记","firstVertex":"单击放置首个顶点","continueLine":"单击继续绘制","finishLine":"单击任何存在的标记以完成","finishPoly":"单击第一个标记以完成","finishRect":"单击完成","startCircle":"单击放置圆心","finishCircle":"单击完成圆形","placeCircleMarker":"点击放置圆形标记"},"actions":{"finish":"完成","cancel":"取消","removeLastVertex":"移除最后的顶点"},"buttonTitles":{"drawMarkerButton":"绘制标记","drawPolyButton":"绘制多边形","drawLineButton":"绘制线段","drawCircleButton":"绘制圆形","drawRectButton":"绘制长方形","editButton":"编辑图层","dragButton":"拖拽图层","cutButton":"剪切图层","deleteButton":"删除图层","drawCircleMarkerButton":"画圆圈标记","snappingButton":"将拖动的标记捕捉到其他图层和顶点","pinningButton":"将共享顶点固定在一起"}}'),g=JSON.parse('{"tooltips":{"placeMarker":"單擊放置標記","firstVertex":"單擊放置第一個頂點","continueLine":"單擊繼續繪製","finishLine":"單擊任何存在的標記以完成","finishPoly":"單擊第一個標記以完成","finishRect":"單擊完成","startCircle":"單擊放置圓心","finishCircle":"單擊完成圓形","placeCircleMarker":"點擊放置圓形標記"},"actions":{"finish":"完成","cancel":"取消","removeLastVertex":"移除最後一個頂點"},"buttonTitles":{"drawMarkerButton":"放置標記","drawPolyButton":"繪製多邊形","drawLineButton":"繪製線段","drawCircleButton":"繪製圓形","drawRectButton":"繪製方形","editButton":"編輯圖形","dragButton":"移動圖形","cutButton":"裁切圖形","deleteButton":"刪除圖形","drawCircleMarkerButton":"畫圓圈標記","snappingButton":"將拖動的標記對齊到其他圖層和頂點","pinningButton":"將共享頂點固定在一起"}}'),_={en:a,de:o,it:s,id:l,ro:h,ru:u,es:c,nl:p,fr:d,pt_br:JSON.parse('{"tooltips":{"placeMarker":"Clique para posicionar o marcador","firstVertex":"Clique para posicionar o primeiro vértice","continueLine":"Clique para continuar desenhando","finishLine":"Clique em qualquer marcador existente para finalizar","finishPoly":"Clique no primeiro ponto para fechar o polígono","finishRect":"Clique para finalizar","startCircle":"Clique para posicionar o centro do círculo","finishCircle":"Clique para fechar o círculo","placeCircleMarker":"Clique para posicionar o marcador circular"},"actions":{"finish":"Finalizar","cancel":"Cancelar","removeLastVertex":"Remover último vértice"},"buttonTitles":{"drawMarkerButton":"Desenhar um marcador","drawPolyButton":"Desenhar um polígono","drawLineButton":"Desenhar uma polilinha","drawCircleButton":"Desenhar um círculo","drawRectButton":"Desenhar um retângulo","editButton":"Editar camada(s)","dragButton":"Mover camada(s)","cutButton":"Recortar camada(s)","deleteButton":"Remover camada(s)","drawCircleMarkerButton":"Marcador de círculos de desenho","snappingButton":"Marcador arrastado para outras camadas e vértices","pinningButton":"Vértices compartilhados de pinos juntos"}}'),zh:f,zh_tw:g,pl:JSON.parse('{"tooltips":{"placeMarker":"Kliknij, aby ustawić znacznik","firstVertex":"Kliknij, aby ustawić pierwszy punkt","continueLine":"Kliknij, aby kontynuować rysowanie","finishLine":"Kliknij dowolny punkt, aby zakończyć","finishPoly":"Kliknij pierwszy punkt, aby zakończyć","finishRect":"Kliknij, aby zakończyć","startCircle":"Kliknij, aby ustawić środek koła","finishCircle":"Kliknij, aby zakończyć rysowanie koła","placeCircleMarker":"Kliknij, aby ustawić okrągły znacznik"},"actions":{"finish":"Zakończ","cancel":"Anuluj","removeLastVertex":"Usuń ostatni punkt"},"buttonTitles":{"drawMarkerButton":"Narysuj znacznik","drawPolyButton":"Narysuj wielokąt","drawLineButton":"Narysuj ścieżkę","drawCircleButton":"Narysuj koło","drawRectButton":"Narysuj prostokąt","editButton":"Edytuj","dragButton":"Przesuń","cutButton":"Wytnij","deleteButton":"Usuń","drawCircleMarkerButton":"Narysuj okrągły znacznik","snappingButton":"Snap przeciągnięty marker na inne warstwy i wierzchołki","pinningButton":"Sworzeń wspólne wierzchołki razem"}}'),sv:JSON.parse('{"tooltips":{"placeMarker":"Klicka för att placera markör","firstVertex":"Klicka för att placera första hörnet","continueLine":"Klicka för att fortsätta rita","finishLine":"Klicka på en existerande punkt för att slutföra","finishPoly":"Klicka på den första punkten för att slutföra","finishRect":"Klicka för att slutföra","startCircle":"Klicka för att placera cirkelns centrum","finishCircle":"Klicka för att slutföra cirkeln","placeCircleMarker":"Klicka för att placera cirkelmarkör"},"actions":{"finish":"Slutför","cancel":"Avbryt","removeLastVertex":"Ta bort sista hörnet"},"buttonTitles":{"drawMarkerButton":"Rita Markör","drawPolyButton":"Rita Polygoner","drawLineButton":"Rita Linje","drawCircleButton":"Rita Cirkel","drawRectButton":"Rita Rektangel","editButton":"Redigera Lager","dragButton":"Dra Lager","cutButton":"Klipp i Lager","deleteButton":"Ta bort Lager","drawCircleMarkerButton":"Rita Cirkelmarkör","snappingButton":"Snäpp dra markören till andra lager och hörn","pinningButton":"Fäst delade hörn tillsammans"}}'),el:JSON.parse('{"tooltips":{"placeMarker":"Κάντε κλικ για να τοποθετήσετε Δείκτη","firstVertex":"Κάντε κλικ για να τοποθετήσετε το πρώτο σημείο","continueLine":"Κάντε κλικ για να συνεχίσετε να σχεδιάζετε","finishLine":"Κάντε κλικ σε οποιονδήποτε υπάρχον σημείο για να ολοκληρωθεί","finishPoly":"Κάντε κλικ στο πρώτο σημείο για να τελειώσετε","finishRect":"Κάντε κλικ για να τελειώσετε","startCircle":"Κάντε κλικ για να τοποθετήσετε κέντρο Κύκλου","finishCircle":"Κάντε κλικ για να ολοκληρώσετε τον Κύκλο","placeCircleMarker":"Κάντε κλικ για να τοποθετήσετε Κυκλικό Δείκτη"},"actions":{"finish":"Τέλος","cancel":"Ακύρωση","removeLastVertex":"Κατάργηση τελευταίου σημείου"},"buttonTitles":{"drawMarkerButton":"Σχεδίαση Δείκτη","drawPolyButton":"Σχεδίαση Πολυγώνου","drawLineButton":"Σχεδίαση Γραμμής","drawCircleButton":"Σχεδίαση Κύκλου","drawRectButton":"Σχεδίαση Ορθογωνίου","editButton":"Επεξεργασία Επιπέδων","dragButton":"Μεταφορά Επιπέδων","cutButton":"Αποκοπή Επιπέδων","deleteButton":"Κατάργηση Επιπέδων","drawCircleMarkerButton":"Σχεδίαση Κυκλικού Δείκτη","snappingButton":"Προσκόλληση του Δείκτη μεταφοράς σε άλλα Επίπεδα και Κορυφές","pinningButton":"Περικοπή κοινών κορυφών μαζί"}}'),hu:JSON.parse('{"tooltips":{"placeMarker":"Kattintson a jelölő elhelyezéséhez","firstVertex":"Kattintson az első pont elhelyezéséhez","continueLine":"Kattintson a következő pont elhelyezéséhez","finishLine":"A befejezéshez kattintson egy meglévő pontra","finishPoly":"A befejezéshez kattintson az első pontra","finishRect":"Kattintson a befejezéshez","startCircle":"Kattintson a kör középpontjának elhelyezéséhez","finishCircle":"Kattintson a kör befejezéséhez","placeCircleMarker":"Kattintson a körjelölő elhelyezéséhez"},"actions":{"finish":"Befejezés","cancel":"Mégse","removeLastVertex":"Utolsó pont eltávolítása"},"buttonTitles":{"drawMarkerButton":"Jelölő rajzolása","drawPolyButton":"Poligon rajzolása","drawLineButton":"Vonal rajzolása","drawCircleButton":"Kör rajzolása","drawRectButton":"Négyzet rajzolása","editButton":"Elemek szerkesztése","dragButton":"Elemek mozgatása","cutButton":"Elemek vágása","deleteButton":"Elemek törlése","drawCircleMarkerButton":"Kör jelölő rajzolása","snappingButton":"Kapcsolja a jelöltőt másik elemhez vagy ponthoz","pinningButton":"Közös pontok összekötése"}}'),da:JSON.parse('{"tooltips":{"placeMarker":"Tryk for at placere en markør","firstVertex":"Tryk for at placere det første punkt","continueLine":"Tryk for at fortsætte linjen","finishLine":"Tryk på et eksisterende punkt for at afslutte","finishPoly":"Tryk på det første punkt for at afslutte","finishRect":"Tryk for at afslutte","startCircle":"Tryk for at placere cirklens center","finishCircle":"Tryk for at afslutte cirklen","placeCircleMarker":"Tryk for at placere en cirkelmarkør"},"actions":{"finish":"Afslut","cancel":"Afbryd","removeLastVertex":"Fjern sidste punkt"},"buttonTitles":{"drawMarkerButton":"Placer markør","drawPolyButton":"Tegn polygon","drawLineButton":"Tegn linje","drawCircleButton":"Tegn cirkel","drawRectButton":"Tegn firkant","editButton":"Rediger","dragButton":"Træk","cutButton":"Klip","deleteButton":"Fjern","drawCircleMarkerButton":"Tegn cirkelmarkør","snappingButton":"Fastgør trukket markør til andre elementer","pinningButton":"Sammenlæg delte elementer"}}'),no:JSON.parse('{"tooltips":{"placeMarker":"Klikk for å plassere punkt","firstVertex":"Klikk for å plassere første punkt","continueLine":"Klikk for å tegne videre","finishLine":"Klikk på et eksisterende punkt for å fullføre","finishPoly":"Klikk første punkt for å fullføre","finishRect":"Klikk for å fullføre","startCircle":"Klikk for å sette sirkel midtpunkt","finishCircle":"Klikk for å fullføre sirkel","placeCircleMarker":"Klikk for å plassere sirkel"},"actions":{"finish":"Fullfør","cancel":"Kanseller","removeLastVertex":"Fjern forrige punkt"},"buttonTitles":{"drawMarkerButton":"Tegn Punkt","drawPolyButton":"Tegn Flate","drawLineButton":"Tegn Linje","drawCircleButton":"Tegn Sirkel","drawRectButton":"Tegn rektangel","editButton":"Rediger Objekter","dragButton":"Dra Objekter","cutButton":"Kutt Objekter","deleteButton":"Fjern Objekter","drawCircleMarkerButton":"Tegn sirkel-punkt","snappingButton":"Fest dratt punkt til andre objekter og punkt","pinningButton":"Pin delte punkt sammen"}}'),fa:JSON.parse('{"tooltips":{"placeMarker":"کلیک برای جانمایی نشان","firstVertex":"کلیک برای رسم اولین رأس","continueLine":"کلیک برای ادامه رسم","finishLine":"کلیک روی هر نشان موجود برای پایان","finishPoly":"کلیک روی اولین نشان برای پایان","finishRect":"کلیک برای پایان","startCircle":"کلیک برای رسم مرکز دایره","finishCircle":"کلیک برای پایان رسم دایره","placeCircleMarker":"کلیک برای رسم نشان دایره"},"actions":{"finish":"پایان","cancel":"لفو","removeLastVertex":"حذف آخرین رأس"},"buttonTitles":{"drawMarkerButton":"درج نشان","drawPolyButton":"رسم چندضلعی","drawLineButton":"رسم خط","drawCircleButton":"رسم دایره","drawRectButton":"رسم چهارضلعی","editButton":"ویرایش لایه‌ها","dragButton":"جابجایی لایه‌ها","cutButton":"برش لایه‌ها","deleteButton":"حذف لایه‌ها","drawCircleMarkerButton":"رسم نشان دایره","snappingButton":"نشانگر را به لایه‌ها و رئوس دیگر بکشید","pinningButton":"رئوس مشترک را با هم پین کنید","rotateButton":"چرخش لایه"}}'),ua:JSON.parse('{"tooltips":{"placeMarker":"Натисніть, щоб нанести маркер","firstVertex":"Натисніть, щоб нанести першу вершину","continueLine":"Натисніть, щоб продовжити малювати","finishLine":"Натисніть будь-який існуючий маркер для завершення","finishPoly":"Виберіть перший маркер, щоб завершити","finishRect":"Натисніть, щоб завершити","startCircle":"Натисніть, щоб додати центр кола","finishCircle":"Натисніть, щоб завершити коло","placeCircleMarker":"Натисніть, щоб нанести круговий маркер"},"actions":{"finish":"Завершити","cancel":"Відмінити","removeLastVertex":"Видалити попередню вершину"},"buttonTitles":{"drawMarkerButton":"Малювати маркер","drawPolyButton":"Малювати полігон","drawLineButton":"Малювати криву","drawCircleButton":"Малювати коло","drawRectButton":"Малювати прямокутник","editButton":"Редагувати шари","dragButton":"Перенести шари","cutButton":"Вирізати шари","deleteButton":"Видалити шари","drawCircleMarkerButton":"Малювати круговий маркер","snappingButton":"Прив’язати перетягнутий маркер до інших шарів та вершин","pinningButton":"Зв\'язати спільні вершини разом"}}'),tr:JSON.parse('{"tooltips":{"placeMarker":"İşaretçi yerleştirmek için tıklayın","firstVertex":"İlk tepe noktasını yerleştirmek için tıklayın","continueLine":"Çizime devam etmek için tıklayın","finishLine":"Bitirmek için mevcut herhangi bir işaretçiyi tıklayın","finishPoly":"Bitirmek için ilk işaretçiyi tıklayın","finishRect":"Bitirmek için tıklayın","startCircle":"Daire merkezine yerleştirmek için tıklayın","finishCircle":"Daireyi bitirmek için tıklayın","placeCircleMarker":"Daire işaretçisi yerleştirmek için tıklayın"},"actions":{"finish":"Bitir","cancel":"İptal","removeLastVertex":"Son köşeyi kaldır"},"buttonTitles":{"drawMarkerButton":"Çizim İşaretçisi","drawPolyButton":"Çokgenler çiz","drawLineButton":"Çoklu çizgi çiz","drawCircleButton":"Çember çiz","drawRectButton":"Dikdörtgen çiz","editButton":"Katmanları düzenle","dragButton":"Katmanları sürükle","cutButton":"Katmanları kes","deleteButton":"Katmanları kaldır","drawCircleMarkerButton":"Daire işaretçisi çiz","snappingButton":"Sürüklenen işaretçiyi diğer katmanlara ve köşelere yapıştır","pinningButton":"Paylaşılan köşeleri birbirine sabitle"}}'),cz:JSON.parse('{"tooltips":{"placeMarker":"Kliknutím vytvoříte značku","firstVertex":"Kliknutím vytvoříte první objekt","continueLine":"Kliknutím pokračujte v kreslení","finishLine":"Kliknutí na libovolnou existující značku pro dokončení","finishPoly":"Vyberte první bod pro dokončení","finishRect":"Klikněte pro dokončení","startCircle":"Kliknutím přidejte střed kruhu","finishCircle":"Нажмите, чтобы задать радиус","placeCircleMarker":"Kliknutím nastavte poloměr"},"actions":{"finish":"Dokončit","cancel":"Zrušit","removeLastVertex":"Zrušit poslední akci"},"buttonTitles":{"drawMarkerButton":"Přidat značku","drawPolyButton":"Nakreslit polygon","drawLineButton":"Nakreslit křivku","drawCircleButton":"Nakreslit kruh","drawRectButton":"Nakreslit obdélník","editButton":"Upravit vrstvu","dragButton":"Přeneste vrstvu","cutButton":"Vyjmout vrstvu","deleteButton":"Smazat vrstvu","drawCircleMarkerButton":"Přidat kruhovou značku","snappingButton":"Navázat tažnou značku k dalším vrstvám a vrcholům","pinningButton":"Spojit společné body dohromady"}}')};function m(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function y(t){for(var e=1;e0&&arguments[0]!==undefined?arguments[0]:this.globalOptions;this.globalEditModeEnabled()?this.disableGlobalEditMode():this.enableGlobalEditMode(t)},handleLayerAdditionInGlobalEditMode:function(){var t=this._addedLayers;for(var e in this._addedLayers={},t){var n=t[e];!!n.pm&&!n._pmTempLayer&&this.globalEditModeEnabled()&&n.pm.enable(y({},this.globalOptions))}},_layerAdded:function(t){var e=t.layer;this._addedLayers[L.stamp(e)]=e}};const k={_globalDragModeEnabled:!1,enableGlobalDragMode:function(){var t=L.PM.Utils.findLayers(this.map);this._globalDragModeEnabled=!0,this._addedLayersDrag={},t.forEach((function(t){t.pm.enableLayerDrag()})),this.throttledReInitDrag||(this.throttledReInitDrag=L.Util.throttle(this.reinitGlobalDragMode,100,this)),this.map.on("layeradd",this.throttledReInitDrag,this),this.map.on("layeradd",this._layerAddedDrag,this),this.Toolbar.toggleButton("dragMode",this.globalDragModeEnabled()),this._fireGlobalDragModeToggled(!0)},disableGlobalDragMode:function(){var t=L.PM.Utils.findLayers(this.map);this._globalDragModeEnabled=!1,t.forEach((function(t){t.pm.disableLayerDrag()})),this.map.off("layeradd",this.throttledReInitDrag,this),this.Toolbar.toggleButton("dragMode",this.globalDragModeEnabled()),this._fireGlobalDragModeToggled(!1)},globalDragModeEnabled:function(){return!!this._globalDragModeEnabled},toggleGlobalDragMode:function(){this.globalDragModeEnabled()?this.disableGlobalDragMode():this.enableGlobalDragMode()},reinitGlobalDragMode:function(){var t=this._addedLayersDrag;for(var e in this._addedLayersDrag={},t){var n=t[e];!!n.pm&&!n._pmTempLayer&&this.globalDragModeEnabled()&&n.pm.enableLayerDrag()}},_layerAddedDrag:function(t){var e=t.layer;this._addedLayersDrag[L.stamp(e)]=e}};const M={_globalRemovalModeEnabled:!1,enableGlobalRemovalMode:function(){var t=this;this._globalRemovalModeEnabled=!0,this.map.eachLayer((function(e){t._isRelevantForRemoval(e)&&(e.pm.disable(),e.on("click",t.removeLayer,t))})),this.throttledReInitRemoval||(this.throttledReInitRemoval=L.Util.throttle(this.reinitGlobalRemovalMode,100,this)),this.map.on("layeradd",this.throttledReInitRemoval,this),this.Toolbar.toggleButton("removalMode",this.globalRemovalModeEnabled()),this._fireGlobalRemovalModeToggled(!0)},disableGlobalRemovalMode:function(){var t=this;this._globalRemovalModeEnabled=!1,this.map.eachLayer((function(e){e.off("click",t.removeLayer,t)})),this.map.off("layeradd",this.throttledReInitRemoval,this),this.Toolbar.toggleButton("removalMode",this.globalRemovalModeEnabled()),this._fireGlobalRemovalModeToggled(!1)},globalRemovalEnabled:function(){return this.globalRemovalModeEnabled()},globalRemovalModeEnabled:function(){return!!this._globalRemovalModeEnabled},toggleGlobalRemovalMode:function(){this.globalRemovalModeEnabled()?this.disableGlobalRemovalMode():this.enableGlobalRemovalMode()},reinitGlobalRemovalMode:function(t){var e=t.layer;this._isRelevantForRemoval(e)&&this.globalRemovalModeEnabled()&&(this.disableGlobalRemovalMode(),this.enableGlobalRemovalMode())},removeLayer:function(t){var e=t.target;this._isRelevantForRemoval(e)&&!e.pm.dragging()&&(e.removeFrom(this.map.pm._getContainingLayer()),e.remove(),e instanceof L.LayerGroup?(this._fireRemoveLayerGroup(e),this._fireRemoveLayerGroup(this.map,e)):(e.pm._fireRemove(e),e.pm._fireRemove(this.map,e)))},_isRelevantForRemoval:function(t){return t.pm&&!(t instanceof L.LayerGroup)&&(!L.PM.optIn&&!t.options.pmIgnore||L.PM.optIn&&!1===t.options.pmIgnore)&&!t._pmTempLayer&&t.pm.options.allowRemoval}};const x={_globalRotateModeEnabled:!1,enableGlobalRotateMode:function(){var t=this;this._globalRotateModeEnabled=!0,L.PM.Utils.findLayers(this.map).filter((function(t){return t instanceof L.Polyline})).forEach((function(e){t._isRelevantForRotate(e)&&e.pm.enableRotate()})),this.throttledReInitRotate||(this.throttledReInitRotate=L.Util.throttle(this._reinitGlobalRotateMode,100,this)),this.map.on("layeradd",this.throttledReInitRotate,this),this.Toolbar.toggleButton("rotateMode",this.globalRotateModeEnabled()),this._fireGlobalRotateModeToggled()},disableGlobalRotateMode:function(){this._globalRotateModeEnabled=!1,L.PM.Utils.findLayers(this.map).filter((function(t){return t instanceof L.Polyline})).forEach((function(t){t.pm.disableRotate()})),this.map.off("layeradd",this.throttledReInitRotate,this),this.Toolbar.toggleButton("rotateMode",this.globalRotateModeEnabled()),this._fireGlobalRotateModeToggled()},globalRotateModeEnabled:function(){return!!this._globalRotateModeEnabled},toggleGlobalRotateMode:function(){this.globalRotateModeEnabled()?this.disableGlobalRotateMode():this.enableGlobalRotateMode()},_reinitGlobalRotateMode:function(t){var e=t.layer;this._isRelevantForRotate(e)&&this.globalRotateModeEnabled()&&(this.disableGlobalRotateMode(),this.enableGlobalRotateMode())},_isRelevantForRotate:function(t){return t.pm&&!(t instanceof L.LayerGroup)&&(!L.PM.optIn&&!t.options.pmIgnore||L.PM.optIn&&!1===t.options.pmIgnore)&&!t._pmTempLayer&&t.pm.options.allowRotation}};function w(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function C(t){for(var e=1;e0&&arguments[0]!==undefined?arguments[0]:"Draw",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this._map,"pm:drawstart",{shape:this._shape,workingLayer:this._layer},t,e)},_fireDrawEnd:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Draw",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this._map,"pm:drawend",{shape:this._shape},t,e)},_fireCreate:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"Draw",n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{};this.__fire(this._map,"pm:create",{shape:this._shape,marker:t,layer:t},e,n)},_fireCenterPlaced:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Draw",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{},n="Draw"===t?this._layer:undefined,i="Draw"!==t?this._layer:undefined;this.__fire(this._layer,"pm:centerplaced",{shape:this._shape,workingLayer:n,layer:i,latlng:this._layer.getLatLng()},t,e)},_fireCut:function(t,e,n){var i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:"Draw",r=arguments.length>4&&arguments[4]!==undefined?arguments[4]:{};this.__fire(t,"pm:cut",{shape:this._shape,layer:e,originalLayer:n},i,r)},_fireEdit:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:this._layer,e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"Edit",n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{};this.__fire(t,"pm:edit",{layer:this._layer,shape:this.getShape()},e,n)},_fireEnable:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Edit",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this._layer,"pm:enable",{layer:this._layer,shape:this.getShape()},t,e)},_fireDisable:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Edit",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this._layer,"pm:disable",{layer:this._layer,shape:this.getShape()},t,e)},_fireUpdate:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Edit",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this._layer,"pm:update",{layer:this._layer,shape:this.getShape()},t,e)},_fireMarkerDragStart:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:undefined,n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Edit",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(this._layer,"pm:markerdragstart",{layer:this._layer,markerEvent:t,shape:this.getShape(),indexPath:e},n,i)},_fireMarkerDrag:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:undefined,n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Edit",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(this._layer,"pm:markerdrag",{layer:this._layer,markerEvent:t,shape:this.getShape(),indexPath:e},n,i)},_fireMarkerDragEnd:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:undefined,n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:undefined,i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:"Edit",r=arguments.length>4&&arguments[4]!==undefined?arguments[4]:{};this.__fire(this._layer,"pm:markerdragend",{layer:this._layer,markerEvent:t,shape:this.getShape(),indexPath:e,intersectionReset:n},i,r)},_fireDragStart:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Edit",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this._layer,"pm:dragstart",{layer:this._layer,shape:this.getShape()},t,e)},_fireDrag:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"Edit",n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{};this.__fire(this._layer,"pm:drag",C(C({},t),{},{shape:this.getShape()}),e,n)},_fireDragEnd:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Edit",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this._layer,"pm:dragend",{layer:this._layer,shape:this.getShape()},t,e)},_fireRemove:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:t,n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Edit",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(t,"pm:remove",{layer:e,shape:this.getShape()},n,i)},_fireVertexAdded:function(t,e,n){var i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:"Edit",r=arguments.length>4&&arguments[4]!==undefined?arguments[4]:{};this.__fire(this._layer,"pm:vertexadded",{layer:this._layer,workingLayer:this._layer,marker:t,indexPath:e,latlng:n,shape:this.getShape()},i,r)},_fireVertexRemoved:function(t,e){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Edit",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(this._layer,"pm:vertexremoved",{layer:this._layer,marker:t,indexPath:e,shape:this.getShape()},n,i)},_fireVertexClick:function(t,e){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Edit",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(this._layer,"pm:vertexclick",{layer:this._layer,markerEvent:t,indexPath:e,shape:this.getShape()},n,i)},_fireIntersect:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"Edit",n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{};this.__fire(this._layer,"pm:intersect",{layer:this._layer,intersection:t,shape:this.getShape()},e,n)},_fireLayerReset:function(t,e){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Edit",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(this._layer,"pm:layerreset",{layer:this._layer,markerEvent:t,indexPath:e,shape:this.getShape()},n,i)},_fireSnapDrag:function(t,e){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Snapping",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(t,"pm:snapdrag",e,n,i)},_fireSnap:function(t,e){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Snapping",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(t,"pm:snap",e,n,i)},_fireUnsnap:function(t,e){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Snapping",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(t,"pm:unsnap",e,n,i)},_fireRotationEnable:function(t,e){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Rotation",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(t,"pm:rotateenable",{layer:this._layer,helpLayer:this._rotatePoly},n,i)},_fireRotationDisable:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"Rotation",n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{};this.__fire(t,"pm:rotatedisable",{layer:this._layer},e,n)},_fireRotationStart:function(t,e){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Rotation",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(t,"pm:rotatestart",{layer:this._rotationLayer,helpLayer:this._layer,startAngle:this._startAngle,originLatLngs:e},n,i)},_fireRotation:function(t,e,n){var i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:"Rotation",r=arguments.length>4&&arguments[4]!==undefined?arguments[4]:{};this.__fire(t,"pm:rotate",{layer:this._rotationLayer,helpLayer:this._layer,startAngle:this._startAngle,angle:this._rotationLayer.pm.getAngle(),angleDiff:e,oldLatLngs:n,newLatLngs:this._rotationLayer.getLatLngs()},i,r)},_fireRotationEnd:function(t,e,n){var i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:"Rotation",r=arguments.length>4&&arguments[4]!==undefined?arguments[4]:{};this.__fire(t,"pm:rotateend",{layer:this._rotationLayer,helpLayer:this._layer,startAngle:e,angle:this._rotationLayer.pm.getAngle(),originLatLngs:n,newLatLngs:this._rotationLayer.getLatLngs()},i,r)},_fireActionClick:function(t,e,n){var i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:"Toolbar",r=arguments.length>4&&arguments[4]!==undefined?arguments[4]:{};this.__fire(this._map,"pm:actionclick",{text:t.text,action:t,btnName:e,button:n},i,r)},_fireButtonClick:function(t,e){var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Toolbar",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(this._map,"pm:buttonclick",{btnName:t,button:e},n,i)},_fireLangChange:function(t,e,n,i){var r=arguments.length>4&&arguments[4]!==undefined?arguments[4]:"Global",a=arguments.length>5&&arguments[5]!==undefined?arguments[5]:{};this.__fire(this.map,"pm:langchange",{oldLang:t,activeLang:e,fallback:n,translations:i},r,a)},_fireGlobalDragModeToggled:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"Global",n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{};this.__fire(this.map,"pm:globaldragmodetoggled",{enabled:t,map:this.map},e,n)},_fireGlobalEditModeToggled:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"Global",n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{};this.__fire(this.map,"pm:globaleditmodetoggled",{enabled:t,map:this.map},e,n)},_fireGlobalRemovalModeToggled:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:"Global",n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{};this.__fire(this.map,"pm:globalremovalmodetoggled",{enabled:t,map:this.map},e,n)},_fireGlobalCutModeToggled:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Global",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this._map,"pm:globalcutmodetoggled",{enabled:!!this._enabled,map:this._map},t,e)},_fireGlobalDrawModeToggled:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Global",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this._map,"pm:globaldrawmodetoggled",{enabled:this._enabled,shape:this._shape,map:this._map},t,e)},_fireGlobalRotateModeToggled:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Global",e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};this.__fire(this.map,"pm:globalrotatemodetoggled",{enabled:this.globalRotateModeEnabled(),map:this.map},t,e)},_fireRemoveLayerGroup:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:t,n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"Edit",i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:{};this.__fire(t,"pm:remove",{layer:e,shape:undefined},n,i)},_fireKeyeventEvent:function(t,e,n){var i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:"Global",r=arguments.length>4&&arguments[4]!==undefined?arguments[4]:{};this.__fire(this.map,"pm:keyevent",{event:t,eventType:e,focusOn:n},i,r)},__fire:function(t,e,n,i){var a=arguments.length>4&&arguments[4]!==undefined?arguments[4]:{};n=r()(n,a,{source:i}),L.PM.Utils._fireEvent(t,e,n)}};const S=E;const O={_lastEvents:{keydown:undefined,keyup:undefined,current:undefined},_initKeyListener:function(t){this.map=t,L.DomEvent.on(document,"keydown keyup",this._onKeyListener,this),L.DomEvent.on(document,"blur",this._onKeyListener,this)},_onKeyListener:function(t){var e="document";this.map.getContainer().contains(t.target)&&(e="map");var n={event:t,eventType:t.type,focusOn:e};this._lastEvents[t.type]=n,this._lastEvents.current=n,this.map.pm._fireKeyeventEvent(t,t.type,e)},getLastKeyEvent:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"current";return this._lastEvents[t]},isShiftKeyPressed:function(){var t;return null===(t=this._lastEvents.current)||void 0===t?void 0:t.event.shiftKey},isAltKeyPressed:function(){var t;return null===(t=this._lastEvents.current)||void 0===t?void 0:t.event.altKey},isCtrlKeyPressed:function(){var t;return null===(t=this._lastEvents.current)||void 0===t?void 0:t.event.ctrlKey},isMetaKeyPressed:function(){var t;return null===(t=this._lastEvents.current)||void 0===t?void 0:t.event.metaKey},getPressedKey:function(){var t;return null===(t=this._lastEvents.current)||void 0===t?void 0:t.event.key}};const D=L.Class.extend({includes:[b,k,M,x,S],initialize:function(t){this.map=t,this.Draw=new L.PM.Draw(t),this.Toolbar=new L.PM.Toolbar(t),this.Keyboard=O,this.globalOptions={snappable:!0,layerGroup:undefined,snappingOrder:["Marker","CircleMarker","Circle","Line","Polygon","Rectangle"],panes:{vertexPane:"markerPane",layerPane:"overlayPane",markerPane:"markerPane"},draggable:!0},this.Keyboard._initKeyListener(t)},setLang:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"en",e=arguments.length>1?arguments[1]:undefined,n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:"en",i=L.PM.activeLang;e&&(_[t]=r()(_[n],e)),L.PM.activeLang=t,this.map.pm.Toolbar.reinit(),this._fireLangChange(i,t,n,_[t])},addControls:function(t){this.Toolbar.addControls(t)},removeControls:function(){this.Toolbar.removeControls()},toggleControls:function(){this.Toolbar.toggleControls()},controlsVisible:function(){return this.Toolbar.isVisible},enableDraw:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Polygon",e=arguments.length>1?arguments[1]:undefined;"Poly"===t&&(t="Polygon"),this.Draw.enable(t,e)},disableDraw:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:"Polygon";"Poly"===t&&(t="Polygon"),this.Draw.disable(t)},setPathOptions:function(t){var e=this,n=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{},i=n.ignoreShapes||[],r=n.merge||!1;this.map.pm.Draw.shapes.forEach((function(n){-1===i.indexOf(n)&&e.map.pm.Draw[n].setPathOptions(t,r)}))},getGlobalOptions:function(){return this.globalOptions},setGlobalOptions:function(t){var e=this,n=r()(this.globalOptions,t),i=!1;this.map.pm.Draw.CircleMarker.enabled()&&this.map.pm.Draw.CircleMarker.options.editable!==n.editable&&(this.map.pm.Draw.CircleMarker.disable(),i=!0),this.map.pm.Draw.shapes.forEach((function(t){e.map.pm.Draw[t].setOptions(n)})),i&&this.map.pm.Draw.CircleMarker.enable(),L.PM.Utils.findLayers(this.map).forEach((function(t){t.pm.setOptions(n)})),this.applyGlobalOptions(),this.globalOptions=n},applyGlobalOptions:function(){L.PM.Utils.findLayers(this.map).forEach((function(t){t.pm.enabled()&&t.pm.applyOptions()}))},globalDrawModeEnabled:function(){return!!this.Draw.getActiveShape()},globalCutModeEnabled:function(){return!!this.Draw.Cut.enabled()},enableGlobalCutMode:function(t){return this.Draw.Cut.enable(t)},toggleGlobalCutMode:function(t){return this.Draw.Cut.toggle(t)},disableGlobalCutMode:function(){return this.Draw.Cut.disable()},getGeomanLayers:function(){var t=arguments.length>0&&arguments[0]!==undefined&&arguments[0],e=L.PM.Utils.findLayers(this.map);if(!t)return e;var n=L.featureGroup();return n._pmTempLayer=!0,e.forEach((function(t){n.addLayer(t)})),n},getGeomanDrawLayers:function(){var t=arguments.length>0&&arguments[0]!==undefined&&arguments[0],e=L.PM.Utils.findLayers(this.map).filter((function(t){return!0===t._drawnByGeoman}));if(!t)return e;var n=L.featureGroup();return n._pmTempLayer=!0,e.forEach((function(t){n.addLayer(t)})),n},_getContainingLayer:function(){return this.globalOptions.layerGroup&&this.globalOptions.layerGroup instanceof L.LayerGroup?this.globalOptions.layerGroup:this.map},_isCRSSimple:function(){return this.map.options.crs===L.CRS.Simple},_touchEventCounter:0,_addTouchEvents:function(t){0===this._touchEventCounter&&(L.DomEvent.on(t,"touchmove",this._canvasTouchMove,this),L.DomEvent.on(t,"touchstart touchend touchcancel",this._canvasTouchClick,this)),this._touchEventCounter+=1},_removeTouchEvents:function(t){1===this._touchEventCounter&&(L.DomEvent.off(t,"touchmove",this._canvasTouchMove,this),L.DomEvent.off(t,"touchstart touchend touchcancel",this._canvasTouchClick,this)),this._touchEventCounter=this._touchEventCounter<=1?0:this._touchEventCounter-1},_canvasTouchMove:function(t){this.map._renderer._onMouseMove(this._createMouseEvent("mousemove",t))},_canvasTouchClick:function(t){var e="";"touchstart"===t.type||"pointerdown"===t.type?e="mousedown":"touchend"===t.type||"pointerup"===t.type?e="mouseup":"touchcancel"!==t.type&&"pointercancel"!==t.type||(e="mouseup"),e&&this.map._renderer._onClick(this._createMouseEvent(e,t))},_createMouseEvent:function(t,e){var n,i=e.touches[0]||e.changedTouches[0];try{n=new MouseEvent(t,{bubbles:e.bubbles,cancelable:e.cancelable,view:e.view,detail:i.detail,screenX:i.screenX,screenY:i.screenY,clientX:i.clientX,clientY:i.clientY,ctrlKey:e.ctrlKey,altKey:e.altKey,shiftKey:e.shiftKey,metaKey:e.metaKey,button:e.button,relatedTarget:e.relatedTarget})}catch(r){(n=document.createEvent("MouseEvents")).initMouseEvent(t,e.bubbles,e.cancelable,e.view,i.detail,i.screenX,i.screenY,i.clientX,i.clientY,e.ctrlKey,e.altKey,e.shiftKey,e.metaKey,e.button,e.relatedTarget)}return n}});var R=n(7361),B=n.n(R),T=n(8721),I=n.n(T);function j(t){var e=L.PM.activeLang;return I()(_,e)||(e="en"),B()(_[e],t)}function G(t){return!function e(t){return t.filter((function(t){return![null,"",undefined].includes(t)})).reduce((function(t,n){return t.concat(Array.isArray(n)?e(n):n)}),[])}(t).length}function A(t){return t.reduce((function(t,e){return 0!==e.length&&t.push(Array.isArray(e)?A(e):e),t}),[])}function N(t,e,n){for(var i,r,a,o=6378137,s=6356752.3142,l=1/298.257223563,h=t.lng,u=t.lat,c=n,p=Math.PI,d=e*p/180,f=Math.sin(d),g=Math.cos(d),_=(1-l)*Math.tan(u*p/180),m=1/Math.sqrt(1+_*_),y=_*m,v=Math.atan2(_,g),b=m*f,k=1-b*b,M=k*(o*o-s*s)/(s*s),x=1+M/16384*(4096+M*(M*(320-175*M)-768)),w=M/1024*(256+M*(M*(74-47*M)-128)),C=c/(s*x),P=2*Math.PI;Math.abs(C-P)>1e-12;){i=Math.cos(2*v+C),P=C,C=c/(s*x)+w*(r=Math.sin(C))*(i+w/4*((a=Math.cos(C))*(2*i*i-1)-w/6*i*(4*r*r-3)*(4*i*i-3)))}var E=y*r-m*a*g,S=Math.atan2(y*a+m*r*g,(1-l)*Math.sqrt(b*b+E*E)),O=l/16*k*(4+l*(4-3*k)),D=h+180*(Math.atan2(r*f,m*a-y*r*g)-(1-O)*l*b*(C+O*r*(i+O*a*(2*i*i-1))))/p,R=180*S/p;return L.latLng(D,R)}function z(t,e,n,i){for(var r,a,o=!(arguments.length>4&&arguments[4]!==undefined)||arguments[4],s=[],l=0;l180?f-360:f<-180?f+360:f,L.latLng([d*r,f])}(e,r,i)}function V(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:t.getLatLngs();return t instanceof L.Polygon?L.polygon(e).getLatLngs():L.polyline(e).getLatLngs()}function F(t,e){var n,i;if(null!==(n=e.options.crs)&&void 0!==n&&null!==(i=n.projection)&&void 0!==i&&i.MAX_LATITUDE){var r,a,o=null===(r=e.options.crs)||void 0===r||null===(a=r.projection)||void 0===a?void 0:a.MAX_LATITUDE;t.lat=Math.max(Math.min(o,t.lat),-o)}return t}function K(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function H(t){for(var e=1;e-1?"pos-right":"",i=L.DomUtil.create("div","button-container ".concat(n),this._container),r=L.DomUtil.create("a","leaflet-buttons-control-button",i);r.setAttribute("role","button"),r.setAttribute("tabindex","0"),r.href="#";var a=L.DomUtil.create("div","leaflet-pm-actions-container ".concat(n),i),o=t.actions,s={cancel:{text:j("actions.cancel"),onClick:function(){this._triggerClick()}},finishMode:{text:j("actions.finish"),onClick:function(){this._triggerClick()}},removeLastVertex:{text:j("actions.removeLastVertex"),onClick:function(){this._map.pm.Draw[t.jsClass]._removeLastVertex()}},finish:{text:j("actions.finish"),onClick:function(e){this._map.pm.Draw[t.jsClass]._finishShape(e)}}};o.forEach((function(i){var r,o="string"==typeof i?i:i.name;if(s[o])r=s[o];else{if(!i.text)return;r=i}var l=L.DomUtil.create("a","leaflet-pm-action ".concat(n," action-").concat(o),a);if(l.setAttribute("role","button"),l.setAttribute("tabindex","0"),l.href="#",l.innerHTML=r.text,L.DomEvent.disableClickPropagation(l),L.DomEvent.on(l,"click",L.DomEvent.stop),r.onClick){L.DomEvent.addListener(l,"click",(function(n){n.preventDefault();var i="",a=e._map.pm.Toolbar.buttons;for(var o in a)if(a[o]._button===t){i=o;break}e._fireActionClick(r,i,t)}),e),L.DomEvent.addListener(l,"click",r.onClick,e)}})),t.toggleStatus&&L.DomUtil.addClass(i,"active");var l=L.DomUtil.create("div","control-icon",r);return t.title&&l.setAttribute("title",t.title),t.iconUrl&&l.setAttribute("src",t.iconUrl),t.className&&L.DomUtil.addClass(l,t.className),L.DomEvent.disableClickPropagation(r),L.DomEvent.on(r,"click",L.DomEvent.stop),L.DomEvent.addListener(r,"click",(function(){e._button.disableOtherButtons&&e._map.pm.Toolbar.triggerClickOnToggledButtons(e);var n="",i=e._map.pm.Toolbar.buttons;for(var r in i)if(i[r]._button===t){n=r;break}e._fireButtonClick(n,t)})),L.DomEvent.addListener(r,"click",this._triggerClick,this),t.disabled&&(L.DomUtil.addClass(r,"pm-disabled"),r.setAttribute("aria-disabled","true")),i},_applyStyleClasses:function(){this._container&&(this._button.toggleStatus&&!1!==this._button.cssToggle?(L.DomUtil.addClass(this.buttonsDomNode,"active"),L.DomUtil.addClass(this._container,"activeChild")):(L.DomUtil.removeClass(this.buttonsDomNode,"active"),L.DomUtil.removeClass(this._container,"activeChild")))},_clicked:function(){this._button.doToggle&&this.toggle()},_updateDisabled:function(){var t="pm-disabled",e=this.buttonsDomNode.children[0];this._button.disabled?(L.DomUtil.addClass(e,t),e.setAttribute("aria-disabled","true")):(L.DomUtil.removeClass(e,t),e.setAttribute("aria-disabled","false"))}});function Y(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function X(t){for(var e=1;e0&&arguments[0]!==undefined?arguments[0]:this.options;"undefined"!=typeof t.editPolygon&&(t.editMode=t.editPolygon),"undefined"!=typeof t.deleteLayer&&(t.removalMode=t.deleteLayer),L.Util.setOptions(this,t),this.applyIconStyle(),this.isVisible=!0,this._showHideButtons()},applyIconStyle:function(){var t=this.getButtons(),e={geomanIcons:{drawMarker:"control-icon leaflet-pm-icon-marker",drawPolyline:"control-icon leaflet-pm-icon-polyline",drawRectangle:"control-icon leaflet-pm-icon-rectangle",drawPolygon:"control-icon leaflet-pm-icon-polygon",drawCircle:"control-icon leaflet-pm-icon-circle",drawCircleMarker:"control-icon leaflet-pm-icon-circle-marker",editMode:"control-icon leaflet-pm-icon-edit",dragMode:"control-icon leaflet-pm-icon-drag",cutPolygon:"control-icon leaflet-pm-icon-cut",removalMode:"control-icon leaflet-pm-icon-delete"}};for(var n in t){var i=t[n];L.Util.setOptions(i,{className:e.geomanIcons[n]})}},removeControls:function(){var t=this.getButtons();for(var e in t)t[e].remove();this.isVisible=!1},toggleControls:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:this.options;this.isVisible?this.removeControls():this.addControls(t)},_addButton:function(t,e){return this.buttons[t]=e,this.options[t]=this.options[t]||!1,this.buttons[t]},triggerClickOnToggledButtons:function(t){var e=["snappingOption"];for(var n in this.buttons)!e.includes(n)&&this.buttons[n]!==t&&this.buttons[n].toggled()&&this.buttons[n]._triggerClick()},toggleButton:function(t,e){var n=!(arguments.length>2&&arguments[2]!==undefined)||arguments[2];return"editPolygon"===t&&(t="editMode"),"deleteLayer"===t&&(t="removalMode"),n&&this.triggerClickOnToggledButtons(this.buttons[t]),!!this.buttons[t]&&this.buttons[t].toggle(e)},_defineButtons:function(){var t=this,e={className:"control-icon leaflet-pm-icon-marker",title:j("buttonTitles.drawMarkerButton"),jsClass:"Marker",onClick:function(){},afterClick:function(e,n){t.map.pm.Draw[n.button._button.jsClass].toggle()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,actions:["cancel"]},n={title:j("buttonTitles.drawPolyButton"),className:"control-icon leaflet-pm-icon-polygon",jsClass:"Polygon",onClick:function(){},afterClick:function(e,n){t.map.pm.Draw[n.button._button.jsClass].toggle()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,actions:["finish","removeLastVertex","cancel"]},i={className:"control-icon leaflet-pm-icon-polyline",title:j("buttonTitles.drawLineButton"),jsClass:"Line",onClick:function(){},afterClick:function(e,n){t.map.pm.Draw[n.button._button.jsClass].toggle()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,actions:["finish","removeLastVertex","cancel"]},r={title:j("buttonTitles.drawCircleButton"),className:"control-icon leaflet-pm-icon-circle",jsClass:"Circle",onClick:function(){},afterClick:function(e,n){t.map.pm.Draw[n.button._button.jsClass].toggle()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,actions:["cancel"]},a={title:j("buttonTitles.drawCircleMarkerButton"),className:"control-icon leaflet-pm-icon-circle-marker",jsClass:"CircleMarker",onClick:function(){},afterClick:function(e,n){t.map.pm.Draw[n.button._button.jsClass].toggle()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,actions:["cancel"]},o={title:j("buttonTitles.drawRectButton"),className:"control-icon leaflet-pm-icon-rectangle",jsClass:"Rectangle",onClick:function(){},afterClick:function(e,n){t.map.pm.Draw[n.button._button.jsClass].toggle()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,actions:["cancel"]},s={title:j("buttonTitles.editButton"),className:"control-icon leaflet-pm-icon-edit",onClick:function(){},afterClick:function(){t.map.pm.toggleGlobalEditMode()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,tool:"edit",actions:["finishMode"]},l={title:j("buttonTitles.dragButton"),className:"control-icon leaflet-pm-icon-drag",onClick:function(){},afterClick:function(){t.map.pm.toggleGlobalDragMode()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,tool:"edit",actions:["finishMode"]},h={title:j("buttonTitles.cutButton"),className:"control-icon leaflet-pm-icon-cut",jsClass:"Cut",onClick:function(){},afterClick:function(e,n){t.map.pm.Draw[n.button._button.jsClass].toggle({snappable:!0,cursorMarker:!0,allowSelfIntersection:!1})},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,tool:"edit",actions:["finish","removeLastVertex","cancel"]},u={title:j("buttonTitles.deleteButton"),className:"control-icon leaflet-pm-icon-delete",onClick:function(){},afterClick:function(){t.map.pm.toggleGlobalRemovalMode()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,tool:"edit",actions:["finishMode"]},c={title:j("buttonTitles.rotateButton"),className:"control-icon leaflet-pm-icon-rotate",onClick:function(){},afterClick:function(){t.map.pm.toggleGlobalRotateMode()},doToggle:!0,toggleStatus:!1,disableOtherButtons:!0,position:this.options.position,tool:"edit",actions:["finishMode"]};this._addButton("drawMarker",new L.Control.PMButton(e)),this._addButton("drawPolyline",new L.Control.PMButton(i)),this._addButton("drawRectangle",new L.Control.PMButton(o)),this._addButton("drawPolygon",new L.Control.PMButton(n)),this._addButton("drawCircle",new L.Control.PMButton(r)),this._addButton("drawCircleMarker",new L.Control.PMButton(a)),this._addButton("editMode",new L.Control.PMButton(s)),this._addButton("dragMode",new L.Control.PMButton(l)),this._addButton("cutPolygon",new L.Control.PMButton(h)),this._addButton("removalMode",new L.Control.PMButton(u)),this._addButton("rotateMode",new L.Control.PMButton(c))},_showHideButtons:function(){if(this.isVisible){this.removeControls(),this.isVisible=!0;var t=this.getButtons(),e=[];for(var n in!1===this.options.drawControls&&(e=e.concat(Object.keys(t).filter((function(e){return!t[e]._button.tool})))),!1===this.options.editControls&&(e=e.concat(Object.keys(t).filter((function(e){return"edit"===t[e]._button.tool})))),!1===this.options.optionsControls&&(e=e.concat(Object.keys(t).filter((function(e){return"options"===t[e]._button.tool})))),!1===this.options.customControls&&(e=e.concat(Object.keys(t).filter((function(e){return"custom"===t[e]._button.tool})))),t)if(this.options[n]&&-1===e.indexOf(n)){var i=t[n]._button.tool;i||(i="draw"),t[n].setPosition(this._getBtnPosition(i)),t[n].addTo(this.map)}}},_getBtnPosition:function(t){return this.options.positions&&this.options.positions[t]?this.options.positions[t]:this.options.position},setBlockPosition:function(t,e){this.options.positions[t]=e,this._showHideButtons(),this.changeControlOrder()},getBlockPositions:function(){return this.options.positions},copyDrawControl:function(t,e){if(!e)throw new TypeError("Button has no name");"object"!==$(e)&&(e={name:e});var n=this._btnNameMapping(t);if(!e.name)throw new TypeError("Button has no name");if(this.buttons[e.name])throw new TypeError("Button with this name already exists");var i=this.map.pm.Draw.createNewDrawInstance(e.name,n);return e=X(X({},this.buttons[n]._button),e),{drawInstance:i,control:this.createCustomControl(e)}},createCustomControl:function(t){if(!t.name)throw new TypeError("Button has no name");if(this.buttons[t.name])throw new TypeError("Button with this name already exists");t.onClick||(t.onClick=function(){}),t.afterClick||(t.afterClick=function(){}),!1!==t.toggle&&(t.toggle=!0),t.block&&(t.block=t.block.toLowerCase()),t.block&&"draw"!==t.block||(t.block=""),t.className?-1===t.className.indexOf("control-icon")&&(t.className="control-icon ".concat(t.className)):t.className="control-icon";var e={tool:t.block,className:t.className,title:t.title||"",jsClass:t.name,onClick:t.onClick,afterClick:t.afterClick,doToggle:t.toggle,toggleStatus:!1,disableOtherButtons:!0,cssToggle:t.toggle,position:this.options.position,actions:t.actions||[],disabled:!!t.disabled};!1!==this.options[t.name]&&(this.options[t.name]=!0);var n=this._addButton(t.name,new L.Control.PMButton(e));return this.changeControlOrder(),n},changeControlOrder:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:[],e=this._shapeMapping(),n=[];t.forEach((function(t){e[t]?n.push(e[t]):n.push(t)}));var i=this.getButtons(),r={};n.forEach((function(t){i[t]&&(r[t]=i[t])}));var a=Object.keys(i).filter((function(t){return!i[t]._button.tool}));a.forEach((function(t){-1===n.indexOf(t)&&(r[t]=i[t])}));var o=Object.keys(i).filter((function(t){return"edit"===i[t]._button.tool}));o.forEach((function(t){-1===n.indexOf(t)&&(r[t]=i[t])}));var s=Object.keys(i).filter((function(t){return"options"===i[t]._button.tool}));s.forEach((function(t){-1===n.indexOf(t)&&(r[t]=i[t])}));var l=Object.keys(i).filter((function(t){return"custom"===i[t]._button.tool}));l.forEach((function(t){-1===n.indexOf(t)&&(r[t]=i[t])})),Object.keys(i).forEach((function(t){-1===n.indexOf(t)&&(r[t]=i[t])})),this.map.pm.Toolbar.buttons=r,this._showHideButtons()},getControlOrder:function(){var t=this.getButtons(),e=[];for(var n in t)e.push(n);return e},changeActionsOfControl:function(t,e){var n=this._btnNameMapping(t);if(!n)throw new TypeError("No name passed");if(!e)throw new TypeError("No actions passed");if(!this.buttons[n])throw new TypeError("Button with this name not exists");this.buttons[n]._button.actions=e,this.changeControlOrder()},setButtonDisabled:function(t,e){var n=this._btnNameMapping(t);e?this.buttons[n].disable():this.buttons[n].enable()},_shapeMapping:function(){return{Marker:"drawMarker",Circle:"drawCircle",Polygon:"drawPolygon",Rectangle:"drawRectangle",Polyline:"drawPolyline",Line:"drawPolyline",CircleMarker:"drawCircleMarker",Edit:"editMode",Drag:"dragMode",Cut:"cutPolygon",Removal:"removalMode",Rotate:"rotateMode"}},_btnNameMapping:function(t){var e=this._shapeMapping();return e[t]?e[t]:t}});function Q(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function tt(t){for(var e=1;e2&&arguments[2]!==undefined?arguments[2]:"asc";if(!e||0===Object.keys(e).length)return function(t,e){return t-e};for(var i,r=Object.keys(e),a=r.length-1,o={};a>=0;)i=r[a],o[i.toLowerCase()]=e[i],a-=1;function s(t){return t instanceof L.Marker?"Marker":t instanceof L.Circle?"Circle":t instanceof L.CircleMarker?"CircleMarker":t instanceof L.Rectangle?"Rectangle":t instanceof L.Polygon?"Polygon":t instanceof L.Polyline?"Line":undefined}return function(e,i){var r,a;if("instanceofShape"===t){if(r=s(e.layer).toLowerCase(),a=s(i.layer).toLowerCase(),!r||!a)return 0}else{if(!e.hasOwnProperty(t)||!i.hasOwnProperty(t))return 0;r=e[t].toLowerCase(),a=i[t].toLowerCase()}var l=r in o?o[r]:Number.MAX_SAFE_INTEGER,h=a in o?o[a]:Number.MAX_SAFE_INTEGER,u=0;return lh&&(u=1),"desc"===n?-1*u:u}}("instanceofShape",i)),t[0]||{}},_checkPrioritiySnapping:function(t){var e=this._map,n=t.segment[0],i=t.segment[1],r=t.latlng,a=this._getDistance(e,n,r),o=this._getDistance(e,i,r),s=a1&&arguments[1]!==undefined&&arguments[1];this.options.pathOptions=e?r()(this.options.pathOptions,t):t},getShapes:function(){return this.shapes},getShape:function(){return this._shape},enable:function(t,e){if(!t)throw new Error("Error: Please pass a shape as a parameter. Possible shapes are: ".concat(this.getShapes().join(",")));this.disable(),this[t].enable(e)},disable:function(){var t=this;this.shapes.forEach((function(e){t[e].disable()}))},addControls:function(){var t=this;this.shapes.forEach((function(e){t[e].addButton()}))},getActiveShape:function(){var t,e=this;return this.shapes.forEach((function(n){e[n]._enabled&&(t=n)})),t},_setGlobalDrawMode:function(){"Cut"===this._shape?this._fireGlobalCutModeToggled():this._fireGlobalDrawModeToggled();var t=L.PM.Utils.findLayers(this._map);this._enabled?t.forEach((function(t){L.PM.Utils.disablePopup(t)})):t.forEach((function(t){L.PM.Utils.enablePopup(t)}))},createNewDrawInstance:function(t,e){var n=this._getShapeFromBtnName(e);if(this[t])throw new TypeError("Draw Type already exists");if(!L.PM.Draw[n])throw new TypeError("There is no class L.PM.Draw.".concat(n));return this[t]=new L.PM.Draw[n](this._map),this[t].toolbarButtonName=t,this[t]._shape=t,this.shapes.push(t),this[e]&&this[t].setOptions(this[e].options),this[t].setOptions(this[t].options),this[t]},_getShapeFromBtnName:function(t){var e={drawMarker:"Marker",drawCircle:"Circle",drawPolygon:"Polygon",drawPolyline:"Line",drawRectangle:"Rectangle",drawCircleMarker:"CircleMarker",editMode:"Edit",dragMode:"Drag",cutPolygon:"Cut",removalMode:"Removal",rotateMode:"Rotate"};return e[t]?e[t]:this[t]?this[t]._shape:t},_finishLayer:function(t){t.pm&&(t.pm.setOptions(this.options),t.pm._shape=this._shape,t.pm._map=this._map),this._addDrawnLayerProp(t)},_addDrawnLayerProp:function(t){t._drawnByGeoman=!0},_setPane:function(t,e){"layerPane"===e?t.options.pane=this._map.pm.globalOptions.panes&&this._map.pm.globalOptions.panes.layerPane||"overlayPane":"vertexPane"===e?t.options.pane=this._map.pm.globalOptions.panes&&this._map.pm.globalOptions.panes.vertexPane||"markerPane":"markerPane"===e&&(t.options.pane=this._map.pm.globalOptions.panes&&this._map.pm.globalOptions.panes.markerPane||"markerPane")},_isFirstLayer:function(){return 0===(this._map||this._layer._map).pm.getGeomanLayers().length}});rt.Marker=rt.extend({initialize:function(t){this._map=t,this._shape="Marker",this.toolbarButtonName="drawMarker"},enable:function(t){var e=this;L.Util.setOptions(this,t),this._enabled=!0,this._map.on("click",this._createMarker,this),this._map.pm.Toolbar.toggleButton(this.toolbarButtonName,!0),this._hintMarker=L.marker([0,0],this.options.markerStyle),this._setPane(this._hintMarker,"markerPane"),this._hintMarker._pmTempLayer=!0,this._hintMarker.addTo(this._map),this.options.tooltips&&this._hintMarker.bindTooltip(j("tooltips.placeMarker"),{permanent:!0,offset:L.point(0,10),direction:"bottom",opacity:.8}).openTooltip(),this._layer=this._hintMarker,this._map.on("mousemove",this._syncHintMarker,this),this.options.markerEditable&&this._map.eachLayer((function(t){e.isRelevantMarker(t)&&t.pm.enable()})),this._fireDrawStart(),this._setGlobalDrawMode()},disable:function(){var t=this;this._enabled&&(this._enabled=!1,this._map.off("click",this._createMarker,this),this._hintMarker.remove(),this._map.off("mousemove",this._syncHintMarker,this),this._map.eachLayer((function(e){t.isRelevantMarker(e)&&e.pm.disable()})),this._map.pm.Toolbar.toggleButton(this.toolbarButtonName,!1),this.options.snappable&&this._cleanupSnapping(),this._fireDrawEnd(),this._setGlobalDrawMode())},enabled:function(){return this._enabled},toggle:function(t){this.enabled()?this.disable():this.enable(t)},isRelevantMarker:function(t){return t instanceof L.Marker&&t.pm&&!t._pmTempLayer},_syncHintMarker:function(t){if(this._hintMarker.setLatLng(t.latlng),this.options.snappable){var e=t;e.target=this._hintMarker,this._handleSnapping(e)}},_createMarker:function(t){if(t.latlng&&(!this.options.requireSnapToFinish||this._hintMarker._snapped||this._isFirstLayer())){this._hintMarker._snapped||this._hintMarker.setLatLng(t.latlng);var e=this._hintMarker.getLatLng(),n=new L.Marker(e,this.options.markerStyle);this._setPane(n,"markerPane"),this._finishLayer(n),n.pm||(n.options.draggable=!1),n.addTo(this._map.pm._getContainingLayer()),n.pm&&this.options.markerEditable?n.pm.enable():n.dragging&&n.dragging.disable(),this._fireCreate(n),this._cleanupSnapping(),this.options.continueDrawing||this.disable()}}});var at=6371008.8,ot={centimeters:637100880,centimetres:637100880,degrees:57.22891354143274,feet:20902260.511392,inches:39.37*at,kilometers:6371.0088,kilometres:6371.0088,meters:at,metres:at,miles:3958.761333810546,millimeters:6371008800,millimetres:6371008800,nauticalmiles:at/1852,radians:1,yards:6967335.223679999};function st(t,e,n){void 0===n&&(n={});var i={type:"Feature"};return(0===n.id||n.id)&&(i.id=n.id),n.bbox&&(i.bbox=n.bbox),i.properties=e||{},i.geometry=t,i}function lt(t,e,n){if(void 0===n&&(n={}),!t)throw new Error("coordinates is required");if(!Array.isArray(t))throw new Error("coordinates must be an Array");if(t.length<2)throw new Error("coordinates must be at least 2 numbers long");if(!gt(t[0])||!gt(t[1]))throw new Error("coordinates must contain numbers");return st({type:"Point",coordinates:t},e,n)}function ht(t,e,n){if(void 0===n&&(n={}),t.length<2)throw new Error("coordinates must be an array of two or more positions");return st({type:"LineString",coordinates:t},e,n)}function ut(t,e){void 0===e&&(e={});var n={type:"FeatureCollection"};return e.id&&(n.id=e.id),e.bbox&&(n.bbox=e.bbox),n.features=t,n}function ct(t,e){void 0===e&&(e="kilometers");var n=ot[e];if(!n)throw new Error(e+" units is invalid");return t*n}function pt(t,e){void 0===e&&(e="kilometers");var n=ot[e];if(!n)throw new Error(e+" units is invalid");return t/n}function dt(t){return 180*(t%(2*Math.PI))/Math.PI}function ft(t){return t%360*Math.PI/180}function gt(t){return!isNaN(t)&&null!==t&&!Array.isArray(t)}function _t(t){var e,n,i={type:"FeatureCollection",features:[]};if("LineString"===(n="Feature"===t.type?t.geometry:t).type)e=[n.coordinates];else if("MultiLineString"===n.type)e=n.coordinates;else if("MultiPolygon"===n.type)e=[].concat.apply([],n.coordinates);else{if("Polygon"!==n.type)throw new Error("Input must be a LineString, MultiLineString, Polygon, or MultiPolygon Feature or Geometry");e=n.coordinates}return e.forEach((function(t){e.forEach((function(e){for(var n=0;n=0&&h<=1&&(p.onLine1=!0),u>=0&&u<=1&&(p.onLine2=!0),!(!p.onLine1||!p.onLine2)&&[p.x,p.y])}function yt(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function vt(t){for(var e=1;e=2&&!Array.isArray(t[0])&&!Array.isArray(t[1]))return t;throw new Error("coord must be GeoJSON Point or an Array of numbers")}function wt(t){if(Array.isArray(t))return t;if("Feature"===t.type){if(null!==t.geometry)return t.geometry.coordinates}else if(t.coordinates)return t.coordinates;throw new Error("coords must be GeoJSON Feature, Geometry Object or an Array")}function Ct(t){return"Feature"===t.type?t.geometry:t}function Pt(t,e){return"FeatureCollection"===t.type?"FeatureCollection":"GeometryCollection"===t.type?"GeometryCollection":"Feature"===t.type&&null!==t.geometry?t.geometry.type:t.type}function Et(t,e,n){if(null!==t)for(var i,r,a,o,s,l,h,u,c=0,p=0,d=t.type,f="FeatureCollection"===d,g="Feature"===d,_=f?t.features.length:1,m=0;m<_;m++){s=(u=!!(h=f?t.features[m].geometry:g?t.geometry:t)&&"GeometryCollection"===h.type)?h.geometries.length:1;for(var y=0;y0){var e=t[t.length-1];this._hintline.setLatLngs([e,this._hintMarker.getLatLng()])}},_syncHintMarker:function(t){if(this._hintMarker.setLatLng(t.latlng),this.options.snappable){var e=t;e.target=this._hintMarker,this._handleSnapping(e)}this.options.allowSelfIntersection||this._handleSelfIntersection(!0,t.latlng)},hasSelfIntersection:function(){return _t(this._layer.toGeoJSON(15)).features.length>0},_handleSelfIntersection:function(t,e){var n=L.polyline(this._layer.getLatLngs());t&&(e||(e=this._hintMarker.getLatLng()),n.addLatLng(e));var i=_t(n.toGeoJSON(15));this._doesSelfIntersect=i.features.length>0,this._doesSelfIntersect?this._hintline.setStyle({color:"#f00000ff"}):this._hintline.isEmpty()||this._hintline.setStyle(this.options.hintlineStyle)},_createVertex:function(t){if(this.options.allowSelfIntersection||(this._handleSelfIntersection(!0,t.latlng),!this._doesSelfIntersect)){this._hintMarker._snapped||this._hintMarker.setLatLng(t.latlng);var e=this._hintMarker.getLatLng();if(e.equals(this._layer.getLatLngs()[0]))this._finishShape(t);else{this._layer._latlngInfo=this._layer._latlngInfo||[],this._layer._latlngInfo.push({latlng:e,snapInfo:this._hintMarker._snapInfo}),this._layer.addLatLng(e);var n=this._createMarker(e);this._setTooltipText(),this._hintline.setLatLngs([e,e]),this._fireVertexAdded(n,undefined,e,"Draw"),"snap"===this.options.finishOn&&this._hintMarker._snapped&&this._finishShape(t)}}},_removeLastVertex:function(){var t=this._layer.getLatLngs(),e=t.pop();if(t.length<1)this.disable();else{var n=this._layerGroup.getLayers().filter((function(t){return t instanceof L.Marker})).filter((function(t){return!L.DomUtil.hasClass(t._icon,"cursor-marker")})).find((function(t){return t.getLatLng()===e})),i=this._layerGroup.getLayers().filter((function(t){return t instanceof L.Marker})),r=L.PM.Utils.findDeepMarkerIndex(i,n).indexPath;this._layerGroup.removeLayer(n),this._layer.setLatLngs(t),this._syncHintLine(),this._setTooltipText(),this._fireVertexRemoved(n,r,"Draw")}},_finishShape:function(){if((this.options.allowSelfIntersection||(this._handleSelfIntersection(!1),!this._doesSelfIntersect))&&(!this.options.requireSnapToFinish||this._hintMarker._snapped||this._isFirstLayer())){var t=this._layer.getLatLngs();if(!(t.length<=1)){var e=L.polyline(t,this.options.pathOptions);this._setPane(e,"layerPane"),this._finishLayer(e),e.addTo(this._map.pm._getContainingLayer()),this._fireCreate(e),this.options.snappable&&this._cleanupSnapping(),this.disable(),this.options.continueDrawing&&this.enable()}}},_createMarker:function(t){var e=new L.Marker(t,{draggable:!1,icon:L.divIcon({className:"marker-icon"})});return this._setPane(e,"vertexPane"),e._pmTempLayer=!0,this._layerGroup.addLayer(e),e.on("click",this._finishShape,this),e},_setTooltipText:function(){var t="";t=j(this._layer.getLatLngs().flat().length<=1?"tooltips.continueLine":"tooltips.finishLine"),this._hintMarker.setTooltipContent(t)}}),rt.Polygon=rt.Line.extend({initialize:function(t){this._map=t,this._shape="Polygon",this.toolbarButtonName="drawPolygon"},_createMarker:function(t){var e=new L.Marker(t,{draggable:!1,icon:L.divIcon({className:"marker-icon"})});return this._setPane(e,"vertexPane"),e._pmTempLayer=!0,this._layerGroup.addLayer(e),1===this._layer.getLatLngs().flat().length?(e.on("click",this._finishShape,this),this._tempSnapLayerIndex=this._otherSnapLayers.push(e)-1,this.options.snappable&&this._cleanupSnapping()):e.on("click",(function(){return 1})),e},_setTooltipText:function(){var t="";t=j(this._layer.getLatLngs().flat().length<=2?"tooltips.continueLine":"tooltips.finishPoly"),this._hintMarker.setTooltipContent(t)},_finishShape:function(){if((this.options.allowSelfIntersection||(this._handleSelfIntersection(!0,this._layer.getLatLngs()[0]),!this._doesSelfIntersect))&&(!this.options.requireSnapToFinish||this._hintMarker._snapped||this._isFirstLayer())){var t=this._layer.getLatLngs();if(!(t.length<=2)){var e=L.polygon(t,this.options.pathOptions);this._setPane(e,"layerPane"),this._finishLayer(e),e.addTo(this._map.pm._getContainingLayer()),this._fireCreate(e),this._cleanupSnapping(),this._otherSnapLayers.splice(this._tempSnapLayerIndex,1),delete this._tempSnapLayerIndex,this.disable(),this.options.continueDrawing&&this.enable()}}}}),rt.Rectangle=rt.extend({initialize:function(t){this._map=t,this._shape="Rectangle",this.toolbarButtonName="drawRectangle"},enable:function(t){if(L.Util.setOptions(this,t),this._enabled=!0,this._layerGroup=new L.LayerGroup,this._layerGroup._pmTempLayer=!0,this._layerGroup.addTo(this._map),this._layer=L.rectangle([[0,0],[0,0]],this.options.pathOptions),this._setPane(this._layer,"layerPane"),this._layer._pmTempLayer=!0,this._startMarker=L.marker([0,0],{icon:L.divIcon({className:"marker-icon rect-start-marker"}),draggable:!1,zIndexOffset:-100,opacity:this.options.cursorMarker?1:0}),this._setPane(this._startMarker,"vertexPane"),this._startMarker._pmTempLayer=!0,this._layerGroup.addLayer(this._startMarker),this._hintMarker=L.marker([0,0],{zIndexOffset:150,icon:L.divIcon({className:"marker-icon cursor-marker"})}),this._setPane(this._hintMarker,"vertexPane"),this._hintMarker._pmTempLayer=!0,this._layerGroup.addLayer(this._hintMarker),this.options.tooltips&&this._hintMarker.bindTooltip(j("tooltips.firstVertex"),{permanent:!0,offset:L.point(0,10),direction:"bottom",opacity:.8}).openTooltip(),this.options.cursorMarker){L.DomUtil.addClass(this._hintMarker._icon,"visible"),this._styleMarkers=[];for(var e=0;e<2;e+=1){var n=L.marker([0,0],{icon:L.divIcon({className:"marker-icon rect-style-marker"}),draggable:!1,zIndexOffset:100});this._setPane(n,"vertexPane"),n._pmTempLayer=!0,this._layerGroup.addLayer(n),this._styleMarkers.push(n)}}this._map._container.style.cursor="crosshair",this._map.on("click",this._placeStartingMarkers,this),this._map.on("mousemove",this._syncHintMarker,this),this._map.pm.Toolbar.toggleButton(this.toolbarButtonName,!0),this._otherSnapLayers=[],this._fireDrawStart(),this._setGlobalDrawMode()},disable:function(){this._enabled&&(this._enabled=!1,this._map._container.style.cursor="",this._map.off("click",this._finishShape,this),this._map.off("click",this._placeStartingMarkers,this),this._map.off("mousemove",this._syncHintMarker,this),this._map.removeLayer(this._layerGroup),this._map.pm.Toolbar.toggleButton(this.toolbarButtonName,!1),this.options.snappable&&this._cleanupSnapping(),this._fireDrawEnd(),this._setGlobalDrawMode())},enabled:function(){return this._enabled},toggle:function(t){this.enabled()?this.disable():this.enable(t)},_placeStartingMarkers:function(t){this._hintMarker._snapped||this._hintMarker.setLatLng(t.latlng);var e=this._hintMarker.getLatLng();L.DomUtil.addClass(this._startMarker._icon,"visible"),this._startMarker.setLatLng(e),this.options.cursorMarker&&this._styleMarkers&&this._styleMarkers.forEach((function(t){L.DomUtil.addClass(t._icon,"visible"),t.setLatLng(e)})),this._map.off("click",this._placeStartingMarkers,this),this._map.on("click",this._finishShape,this),this._hintMarker.setTooltipContent(j("tooltips.finishRect")),this._setRectangleOrigin()},_setRectangleOrigin:function(){var t=this._startMarker.getLatLng();t&&(this._layerGroup.addLayer(this._layer),this._layer.setLatLngs([t,t]),this._hintMarker.on("move",this._syncRectangleSize,this))},_syncHintMarker:function(t){if(this._hintMarker.setLatLng(t.latlng),this.options.snappable){var e=t;e.target=this._hintMarker,this._handleSnapping(e)}},_syncRectangleSize:function(){var t=this,e=F(this._startMarker.getLatLng(),this._map),n=F(this._hintMarker.getLatLng(),this._map),i=L.PM.Utils._getRotatedRectangle(e,n,this.options.rectangleAngle||0,this._map);if(this._layer.setLatLngs(i),this.options.cursorMarker&&this._styleMarkers){var r=[];i.forEach((function(t){t.equals(e,1e-8)||t.equals(n,1e-8)||r.push(t)})),r.forEach((function(e,n){try{t._styleMarkers[n].setLatLng(e)}catch(i){}}))}},_findCorners:function(){var t=this._layer.getBounds();return[t.getNorthWest(),t.getNorthEast(),t.getSouthEast(),t.getSouthWest()]},_finishShape:function(t){this._hintMarker._snapped||this._hintMarker.setLatLng(t.latlng);var e=this._hintMarker.getLatLng(),n=this._startMarker.getLatLng();if(!this.options.requireSnapToFinish||this._hintMarker._snapped||this._isFirstLayer()){var i=L.rectangle([n,e],this.options.pathOptions);if(this.options.rectangleAngle){var r=L.PM.Utils._getRotatedRectangle(n,e,this.options.rectangleAngle||0,this._map);i.setLatLngs(r),i.pm&&i.pm._setAngle(this.options.rectangleAngle||0)}this._setPane(i,"layerPane"),this._finishLayer(i),i.addTo(this._map.pm._getContainingLayer()),this._fireCreate(i),this.disable(),this.options.continueDrawing&&this.enable()}}}),rt.Circle=rt.extend({initialize:function(t){this._map=t,this._shape="Circle",this.toolbarButtonName="drawCircle"},enable:function(t){L.Util.setOptions(this,t),this.options.radius=0,this._enabled=!0,this._layerGroup=new L.LayerGroup,this._layerGroup._pmTempLayer=!0,this._layerGroup.addTo(this._map),this._layer=L.circle([0,0],vt(vt({},this.options.templineStyle),{},{radius:0})),this._setPane(this._layer,"layerPane"),this._layer._pmTempLayer=!0,this._centerMarker=L.marker([0,0],{icon:L.divIcon({className:"marker-icon"}),draggable:!1,zIndexOffset:100}),this._setPane(this._centerMarker,"vertexPane"),this._centerMarker._pmTempLayer=!0,this._hintMarker=L.marker([0,0],{zIndexOffset:110,icon:L.divIcon({className:"marker-icon cursor-marker"})}),this._setPane(this._hintMarker,"vertexPane"),this._hintMarker._pmTempLayer=!0,this._layerGroup.addLayer(this._hintMarker),this.options.cursorMarker&&L.DomUtil.addClass(this._hintMarker._icon,"visible"),this.options.tooltips&&this._hintMarker.bindTooltip(j("tooltips.startCircle"),{permanent:!0,offset:L.point(0,10),direction:"bottom",opacity:.8}).openTooltip(),this._hintline=L.polyline([],this.options.hintlineStyle),this._setPane(this._hintline,"layerPane"),this._hintline._pmTempLayer=!0,this._layerGroup.addLayer(this._hintline),this._map._container.style.cursor="crosshair",this._map.on("click",this._placeCenterMarker,this),this._map.on("mousemove",this._syncHintMarker,this),this._map.pm.Toolbar.toggleButton(this.toolbarButtonName,!0),this._otherSnapLayers=[],this._fireDrawStart(),this._setGlobalDrawMode()},disable:function(){this._enabled&&(this._enabled=!1,this._map._container.style.cursor="",this._map.off("click",this._finishShape,this),this._map.off("click",this._placeCenterMarker,this),this._map.off("mousemove",this._syncHintMarker,this),this._map.removeLayer(this._layerGroup),this._map.pm.Toolbar.toggleButton(this.toolbarButtonName,!1),this.options.snappable&&this._cleanupSnapping(),this._fireDrawEnd(),this._setGlobalDrawMode())},enabled:function(){return this._enabled},toggle:function(t){this.enabled()?this.disable():this.enable(t)},_syncHintLine:function(){var t=this._centerMarker.getLatLng(),e=this._getNewDestinationOfHintMarker();this._hintline.setLatLngs([t,e])},_syncCircleRadius:function(){var t,e=this._centerMarker.getLatLng(),n=this._hintMarker.getLatLng();t=this._map.options.crs===L.CRS.Simple?this._map.distance(e,n):e.distanceTo(n),this.options.minRadiusCircle&&tthis.options.maxRadiusCircle?this._layer.setRadius(this.options.maxRadiusCircle):this._layer.setRadius(t)},_syncHintMarker:function(t){if(this._hintMarker.setLatLng(t.latlng),this._hintMarker.setLatLng(this._getNewDestinationOfHintMarker()),this.options.snappable){var e=t;e.target=this._hintMarker,this._handleSnapping(e)}this._handleHintMarkerSnapping()},_placeCenterMarker:function(t){this._layerGroup.addLayer(this._layer),this._layerGroup.addLayer(this._centerMarker),this._hintMarker._snapped||this._hintMarker.setLatLng(t.latlng);var e=this._hintMarker.getLatLng();this._layerGroup.addLayer(this._layer),this._centerMarker.setLatLng(e),this._map.off("click",this._placeCenterMarker,this),this._map.on("click",this._finishShape,this),this._placeCircleCenter()},_placeCircleCenter:function(){var t=this._centerMarker.getLatLng();t&&(this._layer.setLatLng(t),this._hintMarker.on("move",this._syncHintLine,this),this._hintMarker.on("move",this._syncCircleRadius,this),this._hintMarker.setTooltipContent(j("tooltips.finishCircle")),this._fireCenterPlaced())},_finishShape:function(t){if(!this.options.requireSnapToFinish||this._hintMarker._snapped||this._isFirstLayer()){this._hintMarker._snapped||this._hintMarker.setLatLng(t.latlng);var e,n=this._centerMarker.getLatLng(),i=this._hintMarker.getLatLng();e=this._map.options.crs===L.CRS.Simple?this._map.distance(n,i):n.distanceTo(i),this.options.minRadiusCircle&&ethis.options.maxRadiusCircle&&(e=this.options.maxRadiusCircle);var r=vt(vt({},this.options.pathOptions),{},{radius:e}),a=L.circle(n,r);this._setPane(a,"layerPane"),this._finishLayer(a),a.addTo(this._map.pm._getContainingLayer()),a.pm&&a.pm._updateHiddenPolyCircle(),this._fireCreate(a),this.disable(),this.options.continueDrawing&&this.enable()}},_getNewDestinationOfHintMarker:function(){var t=this._centerMarker.getLatLng(),e=this._hintMarker.getLatLng(),n=t.distanceTo(e);return t.equals(L.latLng([0,0]))||(this.options.minRadiusCircle&&nthis.options.maxRadiusCircle&&(e=U(this._map,t,e,this.options.maxRadiusCircle))),e},_handleHintMarkerSnapping:function(){if(this._hintMarker._snapped){var t=this._centerMarker.getLatLng(),e=this._hintMarker.getLatLng(),n=t.distanceTo(e);t.equals(L.latLng([0,0]))||(this.options.minRadiusCircle&&nthis.options.maxRadiusCircle)&&this._hintMarker.setLatLng(this._hintMarker._orgLatLng)}this._hintMarker.setLatLng(this._getNewDestinationOfHintMarker())}}),rt.CircleMarker=rt.Marker.extend({initialize:function(t){this._map=t,this._shape="CircleMarker",this.toolbarButtonName="drawCircleMarker",this._layerIsDragging=!1},enable:function(t){var e=this;L.Util.setOptions(this,t),this._enabled=!0,this._map.pm.Toolbar.toggleButton(this.toolbarButtonName,!0),this.options.editable?(this._layerGroup=new L.LayerGroup,this._layerGroup._pmTempLayer=!0,this._layerGroup.addTo(this._map),this._layer=L.circleMarker([0,0],this.options.templineStyle),this._setPane(this._layer,"layerPane"),this._layer._pmTempLayer=!0,this._centerMarker=L.marker([0,0],{icon:L.divIcon({className:"marker-icon"}),draggable:!1,zIndexOffset:100}),this._setPane(this._centerMarker,"vertexPane"),this._centerMarker._pmTempLayer=!0,this._hintMarker=L.marker([0,0],{zIndexOffset:110,icon:L.divIcon({className:"marker-icon cursor-marker"})}),this._setPane(this._hintMarker,"vertexPane"),this._hintMarker._pmTempLayer=!0,this._layerGroup.addLayer(this._hintMarker),this.options.cursorMarker&&L.DomUtil.addClass(this._hintMarker._icon,"visible"),this.options.tooltips&&this._hintMarker.bindTooltip(j("tooltips.startCircle"),{permanent:!0,offset:L.point(0,10),direction:"bottom",opacity:.8}).openTooltip(),this._hintline=L.polyline([],this.options.hintlineStyle),this._setPane(this._hintline,"layerPane"),this._hintline._pmTempLayer=!0,this._layerGroup.addLayer(this._hintline),this._map.on("click",this._placeCenterMarker,this),this._map._container.style.cursor="crosshair"):(this._map.on("click",this._createMarker,this),this._hintMarker=L.circleMarker([0,0],this.options.templineStyle),this._setPane(this._hintMarker,"layerPane"),this._hintMarker._pmTempLayer=!0,this._hintMarker.addTo(this._map),this._layer=this._hintMarker,this.options.tooltips&&this._hintMarker.bindTooltip(j("tooltips.placeCircleMarker"),{permanent:!0,offset:L.point(0,10),direction:"bottom",opacity:.8}).openTooltip()),this._map.on("mousemove",this._syncHintMarker,this),!this.options.editable&&this.options.markerEditable&&this._map.eachLayer((function(t){e.isRelevantMarker(t)&&t.pm.enable()})),this._layer.bringToBack(),this._fireDrawStart(),this._setGlobalDrawMode()},disable:function(){var t=this;this._enabled&&(this._enabled=!1,this.options.editable?(this._map._container.style.cursor="",this._map.off("click",this._finishShape,this),this._map.off("click",this._placeCenterMarker,this),this._map.removeLayer(this._layerGroup)):(this._map.off("click",this._createMarker,this),this._map.eachLayer((function(e){t.isRelevantMarker(e)&&e.pm.disable()})),this._hintMarker.remove()),this._map.off("mousemove",this._syncHintMarker,this),this._map.pm.Toolbar.toggleButton(this.toolbarButtonName,!1),this.options.snappable&&this._cleanupSnapping(),this._fireDrawEnd(),this._setGlobalDrawMode())},_placeCenterMarker:function(t){this._layerGroup.addLayer(this._layer),this._layerGroup.addLayer(this._centerMarker),this._hintMarker._snapped||this._hintMarker.setLatLng(t.latlng);var e=this._hintMarker.getLatLng();this._layerGroup.addLayer(this._layer),this._centerMarker.setLatLng(e),this._map.off("click",this._placeCenterMarker,this),this._map.on("click",this._finishShape,this),this._placeCircleCenter()},_placeCircleCenter:function(){var t=this._centerMarker.getLatLng();t&&(this._layer.setLatLng(t),this._hintMarker.on("move",this._syncHintLine,this),this._hintMarker.on("move",this._syncCircleRadius,this),this._hintMarker.setTooltipContent(j("tooltips.finishCircle")),this._fireCenterPlaced())},_syncHintLine:function(){var t=this._centerMarker.getLatLng(),e=this._getNewDestinationOfHintMarker();this._hintline.setLatLngs([t,e])},_syncCircleRadius:function(){var t=this._centerMarker.getLatLng(),e=this._hintMarker.getLatLng(),n=this._map.project(t).distanceTo(this._map.project(e));this.options.minRadiusCircleMarker&&nthis.options.maxRadiusCircleMarker?this._layer.setRadius(this.options.maxRadiusCircleMarker):this._layer.setRadius(n)},_syncHintMarker:function(t){if(this._hintMarker.setLatLng(t.latlng),this._hintMarker.setLatLng(this._getNewDestinationOfHintMarker()),this.options.snappable){var e=t;e.target=this._hintMarker,this._handleSnapping(e)}this._handleHintMarkerSnapping()},isRelevantMarker:function(t){return t instanceof L.CircleMarker&&!(t instanceof L.Circle)&&t.pm&&!t._pmTempLayer},_createMarker:function(t){if((!this.options.requireSnapToFinish||this._hintMarker._snapped||this._isFirstLayer())&&t.latlng&&!this._layerIsDragging){this._hintMarker._snapped||this._hintMarker.setLatLng(t.latlng);var e=this._hintMarker.getLatLng(),n=L.circleMarker(e,this.options.pathOptions);this._setPane(n,"layerPane"),this._finishLayer(n),n.addTo(this._map.pm._getContainingLayer()),n.pm&&this.options.markerEditable&&n.pm.enable(),this._fireCreate(n),this._cleanupSnapping(),this.options.continueDrawing||this.disable()}},_finishShape:function(t){if(!this.options.requireSnapToFinish||this._hintMarker._snapped||this._isFirstLayer()){this._hintMarker._snapped||this._hintMarker.setLatLng(t.latlng);var e=this._centerMarker.getLatLng(),n=this._hintMarker.getLatLng(),i=this._map.project(e).distanceTo(this._map.project(n));this.options.editable&&(this.options.minRadiusCircleMarker&&ithis.options.maxRadiusCircleMarker&&(i=this.options.maxRadiusCircleMarker));var r=kt(kt({},this.options.pathOptions),{},{radius:i}),a=L.circleMarker(e,r);this._setPane(a,"layerPane"),this._finishLayer(a),a.addTo(this._map.pm._getContainingLayer()),a.pm&&a.pm._updateHiddenPolyCircle(),this._fireCreate(a),this.disable(),this.options.continueDrawing&&this.enable()}},_getNewDestinationOfHintMarker:function(){var t=this._hintMarker.getLatLng();if(this.options.editable){var e=this._centerMarker.getLatLng();if(e.equals(L.latLng([0,0])))return t;var n=this._map.project(e).distanceTo(this._map.project(t));this.options.minRadiusCircleMarker&&nthis.options.maxRadiusCircleMarker&&(t=U(this._map,e,t,this._pxRadiusToMeter(this.options.maxRadiusCircleMarker)))}return t},_handleHintMarkerSnapping:function(){if(this.options.editable){if(this._hintMarker._snapped){var t=this._centerMarker.getLatLng(),e=this._hintMarker.getLatLng(),n=this._map.project(t).distanceTo(this._map.project(e));(this.options.minRadiusCircleMarker&&nthis.options.maxRadiusCircleMarker)&&this._hintMarker.setLatLng(this._hintMarker._orgLatLng)}this._hintMarker.setLatLng(this._getNewDestinationOfHintMarker())}},_pxRadiusToMeter:function(t){var e=this._centerMarker.getLatLng(),n=this._map.project(e),i=L.point(n.x+t,n.y);return this._map.unproject(i).distanceTo(e)}});const Rt=function(t){if(!t)throw new Error("geojson is required");var e=[];return Dt(t,(function(t){!function(t,e){var n=[],i=t.geometry;if(null!==i){switch(i.type){case"Polygon":n=wt(i);break;case"LineString":n=[wt(i)]}n.forEach((function(n){var i=function(t,e){var n=[];return t.reduce((function(t,i){var r,a,o,s,l,h,u=ht([t,i],e);return u.bbox=(a=i,o=(r=t)[0],s=r[1],l=a[0],h=a[1],[ol?o:l,s>h?s:h]),n.push(u),i})),n}(n,t.properties);i.forEach((function(t){t.id=e.length,e.push(t)}))}))}}(t,e)})),ut(e)};var Bt=n(1787);function Tt(t,e){var n=wt(t),i=wt(e);if(2!==n.length)throw new Error(" line1 must only contain 2 coordinates");if(2!==i.length)throw new Error(" line2 must only contain 2 coordinates");var r=n[0][0],a=n[0][1],o=n[1][0],s=n[1][1],l=i[0][0],h=i[0][1],u=i[1][0],c=i[1][1],p=(c-h)*(o-r)-(u-l)*(s-a),d=(u-l)*(a-h)-(c-h)*(r-l),f=(o-r)*(a-h)-(s-a)*(r-l);if(0===p)return null;var g=d/p,_=f/p;return g>=0&&g<=1&&_>=0&&_<=1?lt([r+g*(o-r),a+g*(s-a)]):null}const It=function(t,e){var n={},i=[];if("LineString"===t.type&&(t=st(t)),"LineString"===e.type&&(e=st(e)),"Feature"===t.type&&"Feature"===e.type&&null!==t.geometry&&null!==e.geometry&&"LineString"===t.geometry.type&&"LineString"===e.geometry.type&&2===t.geometry.coordinates.length&&2===e.geometry.coordinates.length){var r=Tt(t,e);return r&&i.push(r),ut(i)}var a=Bt();return a.load(Rt(e)),St(Rt(t),(function(t){St(a.search(t),(function(e){var r=Tt(t,e);if(r){var a=wt(r).join(",");n[a]||(n[a]=!0,i.push(r))}}))})),ut(i)};const jt=function(t,e,n){void 0===n&&(n={});var i=xt(t),r=xt(e),a=ft(r[1]-i[1]),o=ft(r[0]-i[0]),s=ft(i[1]),l=ft(r[1]),h=Math.pow(Math.sin(a/2),2)+Math.pow(Math.sin(o/2),2)*Math.cos(s)*Math.cos(l);return ct(2*Math.atan2(Math.sqrt(h),Math.sqrt(1-h)),n.units)};const Gt=function(t){var e=t[0],n=t[1],i=t[2],r=t[3];if(jt(t.slice(0,2),[i,n])>=jt(t.slice(0,2),[e,r])){var a=(n+r)/2;return[e,a-(i-e)/2,i,a+(i-e)/2]}var o=(e+i)/2;return[o-(r-n)/2,n,o+(r-n)/2,r]};function At(t){var e=[Infinity,Infinity,-Infinity,-Infinity];return Et(t,(function(t){e[0]>t[0]&&(e[0]=t[0]),e[1]>t[1]&&(e[1]=t[1]),e[2] is required");if("number"!=typeof n)throw new Error(" must be a number");if("number"!=typeof i)throw new Error(" must be a number");!1!==r&&r!==undefined||(t=JSON.parse(JSON.stringify(t)));var a=Math.pow(10,n);return Et(t,(function(t){!function(t,e,n){t.length>n&&t.splice(n,t.length);for(var i=0;i0&&((g=f.features[0]).properties.dist=jt(e,g,n),g.properties.location=r+jt(s,g,n)),s.properties.dist1&&n.push(ht(h)),ut(n)}function qt(t,e){if(!e.features.length)throw new Error("lines must contain features");if(1===e.features.length)return e.features[0];var n,i=Infinity;return St(e,(function(e){var r=Ft(e,t).properties.dist;r=t[0]&&e[3]>=t[1]}(i,o))return!1;"Polygon"===a&&(s=[s]);for(var l=!1,h=0;ht[1]!=h>t[1]&&t[0]<(l-o)*(t[1]-s)/(h-s)+o&&(i=!i)}return i}function $t(t,e,n,i,r){var a=n[0],o=n[1],s=t[0],l=t[1],h=e[0],u=e[1],c=h-s,p=u-l,d=(n[0]-s)*p-(n[1]-l)*c;if(null!==r){if(Math.abs(d)>r)return!1}else if(0!==d)return!1;return i?"start"===i?Math.abs(c)>=Math.abs(p)?c>0?s0?l=Math.abs(p)?c>0?s<=a&&a0?l<=o&&o=Math.abs(p)?c>0?s0?l=Math.abs(p)?c>0?s<=a&&a<=h:h<=a&&a<=s:p>0?l<=o&&o<=u:u<=o&&o<=l}const Wt=function(t,e,n){void 0===n&&(n={});for(var i=xt(t),r=wt(e),a=0;ae[0])&&(!(t[2]e[1])&&!(t[3]1?e.forEach((function(t){i.push(function(t){return ae({type:"LineString",coordinates:t})}(t))})):i.push(t),i}function pe(t){var e=[];return t.eachLayer((function(t){e.push(se(t.toGeoJSON(15)))})),function(t){return ae({type:"MultiLineString",coordinates:t})}(e)}function de(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var n=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null==n)return;var i,r,a=[],o=!0,s=!1;try{for(n=n.call(t);!(o=(i=n.next()).done)&&(a.push(i.value),!e||a.length!==e);o=!0);}catch(l){s=!0,r=l}finally{try{o||null==n["return"]||n["return"]()}finally{if(s)throw r}}return a}(t,e)||function(t,e){if(!t)return;if("string"==typeof t)return fe(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);"Object"===n&&t.constructor&&(n=t.constructor.name);if("Map"===n||"Set"===n)return Array.from(t);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return fe(t,e)}(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function fe(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);n0)||e.options.layersToCut.indexOf(t)>-1})).filter((function(t){return!e._layerGroup.hasLayer(t)})).filter((function(e){try{var n=!!It(t.toGeoJSON(15),e.toGeoJSON(15)).features.length>0;return n||e instanceof L.Polyline&&!(e instanceof L.Polygon)?n:(i=t.toGeoJSON(15),r=e.toGeoJSON(15),a=oe(i),o=oe(r),!(0===(s=re().intersection(a.coordinates,o.coordinates)).length||!(1===s.length?le(s[0]):he(s))))}catch(l){return e instanceof L.Polygon&&console.error("You can't cut polygons with self-intersections"),!1}var i,r,a,o,s})).forEach((function(n){var r;if(n instanceof L.Polygon){var a=(r=L.polygon(n.getLatLngs())).getLatLngs();i.forEach((function(t){if(t&&t.snapInfo){var n=t.latlng,i=e._calcClosestLayer(n,[r]);if(i&&i.segment&&i.distance1?B()(a,h):a).splice(u,0,n)}}}}))}else r=n;var o=e._cutLayer(t,r),s=L.geoJSON(o,n.options);if(1===s.getLayers().length){var l=s.getLayers();s=de(l,1)[0]}e._setPane(s,"layerPane");var h=s.addTo(e._map.pm._getContainingLayer());if(h.pm.enable(n.pm.options),h.pm.disable(),n._pmTempLayer=!0,t._pmTempLayer=!0,n.remove(),n.removeFrom(e._map.pm._getContainingLayer()),t.remove(),t.removeFrom(e._map.pm._getContainingLayer()),h.getLayers&&0===h.getLayers().length&&e._map.pm.removeLayer({target:h}),h instanceof L.LayerGroup?(h.eachLayer((function(t){e._addDrawnLayerProp(t)})),e._addDrawnLayerProp(h)):e._addDrawnLayerProp(h),e.options.layersToCut&&L.Util.isArray(e.options.layersToCut)&&e.options.layersToCut.length>0){var u=e.options.layersToCut.indexOf(n);u>-1&&e.options.layersToCut.splice(u,1)}e._editedLayers.push({layer:h,originalLayer:n})}))},_cutLayer:function(t,e){var n,i,r,a,o,s,l=L.geoJSON();if(e instanceof L.Polygon)i=e.toGeoJSON(15),r=t.toGeoJSON(15),a=oe(i),o=oe(r),n=0===(s=re().difference(a.coordinates,o.coordinates)).length?null:1===s.length?le(s[0]):he(s);else{var h=ce(e);h.forEach((function(e){var n=Yt(e,t.toGeoJSON(15));(n&&n.features.length>0?L.geoJSON(n):L.geoJSON(e)).getLayers().forEach((function(e){Qt(t.toGeoJSON(15),e.toGeoJSON(15))||e.addTo(l)}))})),n=h.length>1?pe(l):l.toGeoJSON(15)}return n}});const ge={enableLayerDrag:function(){var t;if(this.options.draggable&&this._layer._map){this.disable(),this._layerDragEnabled=!0,this._map||(this._map=this._layer._map),(this._layer instanceof L.Marker||this._layer instanceof L.ImageOverlay)&&L.DomEvent.on(this._getDOMElem(),"dragstart",this._stopDOMImageDrag),this._layer.dragging&&this._layer.dragging.disable(),this._tempDragCoord=null,null!==(t=this._layer._map)&&void 0!==t&&t.options.preferCanvas?(this._layer.on("mouseout",this.removeDraggingClass,this),this._layer.on("mouseover",this.addDraggingClass,this)):this.addDraggingClass(),this._originalMapDragState=this._layer._map.dragging._enabled,this._safeToCacheDragState=!0;var e,n=this._getDOMElem();if(n)null!==(e=this._layer._map)&&void 0!==e&&e.options.preferCanvas&&this._layer._renderer?(this._layer.on("touchstart mousedown",this._dragMixinOnMouseDown,this),this._map.pm._addTouchEvents(n)):L.DomEvent.on(n,"touchstart mousedown",this._simulateMouseDownEvent,this)}},disableLayerDrag:function(){var t;this._layerDragEnabled=!1,null!==(t=this._layer._map)&&void 0!==t&&t.options.preferCanvas?(this._layer.off("mouseout",this.removeDraggingClass,this),this._layer.off("mouseover",this.addDraggingClass,this)):this.removeDraggingClass(),this._originalMapDragState&&this._dragging&&this._map.dragging.enable(),this._safeToCacheDragState=!1,this._layer.dragging&&this._layer.dragging.disable();var e,n=this._getDOMElem();n&&(null!==(e=this._layer._map)&&void 0!==e&&e.options.preferCanvas&&this._layer._renderer?(this._layer.off("touchstart mousedown",this._dragMixinOnMouseDown,this),this._map.pm._removeTouchEvents(n)):L.DomEvent.off(n,"touchstart mousedown",this._simulateMouseDownEvent,this))},dragging:function(){return this._dragging},layerDragEnabled:function(){return!!this._layerDragEnabled},_simulateMouseDownEvent:function(t){var e={originalEvent:t,target:this._layer},n=t.touches?t.touches[0]:t;return e.containerPoint=this._map.mouseEventToContainerPoint(n),e.latlng=this._map.containerPointToLatLng(e.containerPoint),this._dragMixinOnMouseDown(e),!1},_simulateMouseMoveEvent:function(t){var e={originalEvent:t,target:this._layer},n=t.touches?t.touches[0]:t;return e.containerPoint=this._map.mouseEventToContainerPoint(n),e.latlng=this._map.containerPointToLatLng(e.containerPoint),this._dragMixinOnMouseMove(e),!1},_simulateMouseUpEvent:function(t){var e={originalEvent:t,target:this._layer};return-1===t.type.indexOf("touch")&&(e.containerPoint=this._map.mouseEventToContainerPoint(t),e.latlng=this._map.containerPointToLatLng(e.containerPoint)),this._dragMixinOnMouseUp(e),!1},_dragMixinOnMouseDown:function(t){if(!(t.originalEvent.button>0)){this._overwriteEventIfItComesFromMarker(t);var e=t._fromLayerSync,n=this._syncLayers("_dragMixinOnMouseDown",t);this._layer instanceof L.Marker&&(!this.options.snappable||e||n?this._disableSnapping():this._initSnappableMarkers()),this._layer instanceof L.CircleMarker&&!(this._layer instanceof L.Circle)&&(!this.options.snappable||e||n?this._layer.pm.options.editable?this._layer.pm._disableSnapping():this._layer.pm._disableSnappingDrag():this._layer.pm.options.editable||this._initSnappableMarkersDrag()),this._safeToCacheDragState&&(this._originalMapDragState=this._layer._map.dragging._enabled,this._safeToCacheDragState=!1),this._tempDragCoord=t.latlng,L.DomEvent.on(this._map.getContainer(),"touchend mouseup",this._simulateMouseUpEvent,this),L.DomEvent.on(this._map.getContainer(),"touchmove mousemove",this._simulateMouseMoveEvent,this)}},_dragMixinOnMouseMove:function(t){this._overwriteEventIfItComesFromMarker(t);var e=this._getDOMElem();this._syncLayers("_dragMixinOnMouseMove",t),this._dragging||(this._dragging=!0,L.DomUtil.addClass(e,"leaflet-pm-dragging"),this._layer instanceof L.Marker||this._layer.bringToFront(),this._originalMapDragState&&this._map.dragging.disable(),this._fireDragStart()),this._tempDragCoord||(this._tempDragCoord=t.latlng),this._onLayerDrag(t),this._layer instanceof L.CircleMarker&&this._layer.pm._updateHiddenPolyCircle()},_dragMixinOnMouseUp:function(t){var e=this,n=this._getDOMElem();return this._syncLayers("_dragMixinOnMouseUp",t),this._originalMapDragState&&this._map.dragging.enable(),this._safeToCacheDragState=!0,L.DomEvent.off(this._map.getContainer(),"touchmove mousemove",this._simulateMouseMoveEvent,this),L.DomEvent.off(this._map.getContainer(),"touchend mouseup",this._simulateMouseUpEvent,this),!!this._dragging&&(this._layer instanceof L.CircleMarker&&this._layer.pm._updateHiddenPolyCircle(),window.setTimeout((function(){e._dragging=!1,n&&L.DomUtil.removeClass(n,"leaflet-pm-dragging"),e._fireDragEnd(),e._fireEdit(),e._layerEdited=!0}),10),!0)},_onLayerDrag:function(t){var e=t.latlng,n=e.lat-this._tempDragCoord.lat,i=e.lng-this._tempDragCoord.lng,r=function u(t){return t.map((function(t){return Array.isArray(t)?u(t):{lat:t.lat+n,lng:t.lng+i}}))};if(this._layer instanceof L.Circle||this._layer instanceof L.CircleMarker&&this._layer.options.editable){var a=r([this._layer.getLatLng()]);this._layer.setLatLng(a[0])}else if(this._layer instanceof L.CircleMarker||this._layer instanceof L.Marker){var o=this._layer.getLatLng();this._layer._snapped&&(o=this._layer._orgLatLng);var s=r([o]);this._layer.setLatLng(s[0])}else if(this._layer instanceof L.ImageOverlay){var l=r([this._layer.getBounds().getNorthWest(),this._layer.getBounds().getSouthEast()]);this._layer.setBounds(l)}else{var h=r(this._layer.getLatLngs());this._layer.setLatLngs(h)}this._tempDragCoord=e,t.layer=this._layer,this._fireDrag(t)},addDraggingClass:function(){var t=this._getDOMElem();t&&L.DomUtil.addClass(t,"leaflet-pm-draggable")},removeDraggingClass:function(){var t=this._getDOMElem();t&&L.DomUtil.removeClass(t,"leaflet-pm-draggable")},_getDOMElem:function(){var t=null;return this._layer._path?t=this._layer._path:this._layer._renderer&&this._layer._renderer._container?t=this._layer._renderer._container:this._layer._image?t=this._layer._image:this._layer._icon&&(t=this._layer._icon),t},_overwriteEventIfItComesFromMarker:function(t){t.target.getLatLng&&(!t.target._radius||t.target._radius<=10)&&(t.containerPoint=this._map.mouseEventToContainerPoint(t.originalEvent),t.latlng=this._map.containerPointToLatLng(t.containerPoint))},_syncLayers:function(t,e){var n=this;if(this.enabled())return!1;if(!e._fromLayerSync&&this._layer===e.target&&this.options.syncLayersOnDrag){e._fromLayerSync=!0;var i=[];if(L.Util.isArray(this.options.syncLayersOnDrag))i=this.options.syncLayersOnDrag,this.options.syncLayersOnDrag.forEach((function(t){t instanceof L.LayerGroup&&(i=i.concat(t.pm.getLayers(!0)))}));else if(!0===this.options.syncLayersOnDrag&&this._parentLayerGroup)for(var r in this._parentLayerGroup){var a=this._parentLayerGroup[r];a.pm&&(i=a.pm.getLayers(!0))}return L.Util.isArray(i)&&i.length>0&&(i=i.filter((function(t){return!!t.pm})).filter((function(t){return!!t.pm.options.draggable}))).forEach((function(i){i!==n._layer&&i.pm[t]&&(i._snapped=!1,i.pm[t](e))})),i.length>0}return!1},_stopDOMImageDrag:function(t){return t.preventDefault(),!1}};function _e(t,e,n){var i=n.getMaxZoom();if(i===Infinity&&(i=n.getZoom()),L.Util.isArray(t)){var r=[];return t.forEach((function(t){r.push(_e(t,e,n))})),r}return t instanceof L.LatLng?function(t,e,n,i){return n.unproject(e.transform(n.project(t,i)),i)}(t,e,n,i):null}function me(t,e){e instanceof L.Layer&&(e=e.getLatLng());var n=t.getMaxZoom();return n===Infinity&&(n=t.getZoom()),t.project(e,n)}function ye(t,e){var n=t.getMaxZoom();return n===Infinity&&(n=t.getZoom()),t.unproject(e,n)}var ve={_onRotateStart:function(t){this._preventRenderingMarkers(!0),this._rotationOriginLatLng=this._getRotationCenter().clone(),this._rotationOriginPoint=me(this._map,this._rotationOriginLatLng),this._rotationStartPoint=me(this._map,t.target.getLatLng()),this._initialRotateLatLng=V(this._layer),this._startAngle=this.getAngle();var e=V(this._rotationLayer,this._rotationLayer.pm._rotateOrgLatLng);this._fireRotationStart(this._rotationLayer,e),this._fireRotationStart(this._map,e)},_onRotate:function(t){var e=me(this._map,t.target.getLatLng()),n=this._rotationStartPoint,i=this._rotationOriginPoint,r=Math.atan2(e.y-i.y,e.x-i.x)-Math.atan2(n.y-i.y,n.x-i.x);this._layer.setLatLngs(this._rotateLayer(r,this._initialRotateLatLng,this._rotationOriginLatLng,L.PM.Matrix.init(),this._map));var a=this;!function h(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:[],n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:-1;if(n>-1&&e.push(n),L.Util.isArray(t[0]))t.forEach((function(t,n){return h(t,e.slice(),n)}));else{var i=B()(a._markers,e);t.forEach((function(t,e){i[e].setLatLng(t)}))}}(this._layer.getLatLngs());var o=V(this._rotationLayer);this._rotationLayer.setLatLngs(this._rotateLayer(r,this._rotationLayer.pm._rotateOrgLatLng,this._rotationOriginLatLng,L.PM.Matrix.init(),this._map));var s=180*r/Math.PI,l=(s=s<0?s+360:s)+this._startAngle;this._setAngle(l),this._rotationLayer.pm._setAngle(l),this._fireRotation(this._rotationLayer,s,o),this._fireRotation(this._map,s,o)},_onRotateEnd:function(){var t=this._startAngle;delete this._rotationOriginLatLng,delete this._rotationOriginPoint,delete this._rotationStartPoint,delete this._initialRotateLatLng,delete this._startAngle;var e=V(this._rotationLayer,this._rotationLayer.pm._rotateOrgLatLng);this._rotationLayer.pm._rotateOrgLatLng=V(this._rotationLayer),this._fireRotationEnd(this._rotationLayer,t,e),this._fireRotationEnd(this._map,t,e),this._rotationLayer.pm._fireEdit(this._rotationLayer,"Rotation"),this._preventRenderingMarkers(!1)},_rotateLayer:function(t,e,n,i,r){var a=me(r,n);return this._matrix=i.clone().rotate(t,a).flip(),_e(e,this._matrix,r)},_setAngle:function(t){t=t<0?t+360:t,this._angle=t%360},_getRotationCenter:function(){var t=L.polygon(this._layer.getLatLngs(),{stroke:!1,fill:!1,pmIgnore:!0}).addTo(this._layer._map),e=t.getCenter();return t.removeFrom(this._layer._map),e},enableRotate:function(){if(this.options.allowRotation){this._rotatePoly=L.polygon(this._layer.getLatLngs(),{fill:!1,stroke:!1,pmIgnore:!1,snapIgnore:!0}).addTo(this._layer._map),this._rotatePoly.pm._setAngle(this.getAngle()),this._rotatePoly.pm.setOptions(this._layer._map.pm.getGlobalOptions()),this._rotatePoly.pm.setOptions({rotate:!0,snappable:!1,hideMiddleMarkers:!0}),this._rotatePoly.pm._rotationLayer=this._layer,this._rotatePoly.pm.enable(),this._rotateOrgLatLng=V(this._layer),this._rotateEnabled=!0,this._layer.on("remove",this.disableRotate,this),this._fireRotationEnable(this._layer),this._fireRotationEnable(this._layer._map)}else this.disableRotate()},disableRotate:function(){this.rotateEnabled()&&(this._rotatePoly.pm.disable(),this._rotatePoly.remove(),this._rotatePoly.pm.setOptions({rotate:!1}),this._rotatePoly=undefined,this._rotateOrgLatLng=undefined,this._layer.off("remove",this.disableRotate,this),this._rotateEnabled=!1,this._fireRotationDisable(this._layer),this._fireRotationDisable(this._layer._map))},rotateEnabled:function(){return this._rotateEnabled},rotateLayer:function(t){var e=t*(Math.PI/180);this._layer.setLatLngs(this._rotateLayer(e,this._layer.getLatLngs(),this._getRotationCenter(),L.PM.Matrix.init(),this._layer._map)),this._rotateOrgLatLng=L.polygon(this._layer.getLatLngs()).getLatLngs(),this._setAngle(this.getAngle()+t),this.rotateEnabled()&&this._rotatePoly&&this._rotatePoly.pm.enabled()&&(this._rotatePoly.setLatLngs(this._rotateLayer(e,this._rotatePoly.getLatLngs(),this._getRotationCenter(),L.PM.Matrix.init(),this._rotatePoly._map)),this._rotatePoly.pm._initMarkers())},rotateLayerToAngle:function(t){var e=t-this.getAngle();this.rotateLayer(e)},getAngle:function(){return this._angle||0}};const Le=ve;const be=L.Class.extend({includes:[ge,it,Le,S],options:{snappable:!0,snapDistance:20,allowSelfIntersection:!0,allowSelfIntersectionEdit:!1,preventMarkerRemoval:!1,removeLayerBelowMinVertexCount:!0,limitMarkersToCount:-1,hideMiddleMarkers:!1,snapSegment:!0,syncLayersOnDrag:!1,draggable:!0,allowEditing:!0,allowRemoval:!0,allowCutting:!0,allowRotation:!0,addVertexOn:"click",removeVertexOn:"contextmenu",removeVertexValidation:undefined,addVertexValidation:undefined,moveVertexValidation:undefined},setOptions:function(t){L.Util.setOptions(this,t)},getOptions:function(){return this.options},applyOptions:function(){},isPolygon:function(){return this._layer instanceof L.Polygon},getShape:function(){return this._shape},_setPane:function(t,e){"layerPane"===e?t.options.pane=this._map.pm.globalOptions.panes&&this._map.pm.globalOptions.panes.layerPane||"overlayPane":"vertexPane"===e?t.options.pane=this._map.pm.globalOptions.panes&&this._map.pm.globalOptions.panes.vertexPane||"markerPane":"markerPane"===e&&(t.options.pane=this._map.pm.globalOptions.panes&&this._map.pm.globalOptions.panes.markerPane||"markerPane")},remove:function(){(this._map||this._layer._map).pm.removeLayer({target:this._layer})},_vertexValidation:function(t,e){var n=e.target,i={layer:this._layer,marker:n,event:e},r="";return"move"===t?r="moveVertexValidation":"add"===t?r="addVertexValidation":"remove"===t&&(r="removeVertexValidation"),this.options[r]&&"function"==typeof this.options[r]&&!this.options[r](i)?("move"===t&&(n._cancelDragEventChain=n.getLatLng()),!1):(n._cancelDragEventChain=null,!0)},_vertexValidationDrag:function(t){return!t._cancelDragEventChain||(t._latlng=t._cancelDragEventChain,t.update(),!1)},_vertexValidationDragEnd:function(t){return!t._cancelDragEventChain||(t._cancelDragEventChain=null,!1)}});function ke(t){return function(t){if(Array.isArray(t))return Me(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||function(t,e){if(!t)return;if("string"==typeof t)return Me(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);"Object"===n&&t.constructor&&(n=t.constructor.name);if("Map"===n||"Set"===n)return Array.from(t);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return Me(t,e)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function Me(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);n0&&e._getMap()&&e._getMap().pm.globalEditModeEnabled()&&e.enabled()&&e.enable(e.getOptions())}}),100,this),this),this._layerGroup.on("layerremove",(function(t){e._removeLayerFromGroup(t.target)}),this);this._layerGroup.on("layerremove",L.Util.throttle((function(t){t.target._pmTempLayer||(e._layers=e.getLayers())}),100,this),this)},enable:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:[];0===e.length&&(this._layers=this.getLayers()),this._options=t,this._layers.forEach((function(n){n instanceof L.LayerGroup?-1===e.indexOf(n._leaflet_id)&&(e.push(n._leaflet_id),n.pm.enable(t,e)):n.pm.enable(t)}))},disable:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:[];0===t.length&&(this._layers=this.getLayers()),this._layers.forEach((function(e){e instanceof L.LayerGroup?-1===t.indexOf(e._leaflet_id)&&(t.push(e._leaflet_id),e.pm.disable(t)):e.pm.disable()}))},enabled:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:[];0===t.length&&(this._layers=this.getLayers());var e=this._layers.find((function(e){return e instanceof L.LayerGroup?-1===t.indexOf(e._leaflet_id)&&(t.push(e._leaflet_id),e.pm.enabled(t)):e.pm.enabled()}));return!!e},toggleEdit:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:[];0===e.length&&(this._layers=this.getLayers()),this._options=t,this._layers.forEach((function(n){n instanceof L.LayerGroup?-1===e.indexOf(n._leaflet_id)&&(e.push(n._leaflet_id),n.pm.toggleEdit(t,e)):n.pm.toggleEdit(t)}))},_initLayer:function(t){var e=L.Util.stamp(this._layerGroup);t.pm._parentLayerGroup||(t.pm._parentLayerGroup={}),t.pm._parentLayerGroup[e]=this._layerGroup},_removeLayerFromGroup:function(t){if(t.pm&&t.pm._layerGroup){var e=L.Util.stamp(this._layerGroup);delete t.pm._layerGroup[e]}},dragging:function(){if(this._layers=this.getLayers(),this._layers){var t=this._layers.find((function(t){return t.pm.dragging()}));return!!t}return!1},getOptions:function(){return this.options},_getMap:function(){var t;return this._map||(null===(t=this._layers.find((function(t){return!!t._map})))||void 0===t?void 0:t._map)||null},getLayers:function(){var t=arguments.length>0&&arguments[0]!==undefined&&arguments[0],e=!(arguments.length>1&&arguments[1]!==undefined)||arguments[1],n=!(arguments.length>2&&arguments[2]!==undefined)||arguments[2],i=arguments.length>3&&arguments[3]!==undefined?arguments[3]:[],r=[];return t?this._layerGroup.getLayers().forEach((function(t){r.push(t),t instanceof L.LayerGroup&&-1===i.indexOf(t._leaflet_id)&&(i.push(t._leaflet_id),r=r.concat(t.pm.getLayers(!0,!0,!0,i)))})):r=this._layerGroup.getLayers(),n&&(r=r.filter((function(t){return!(t instanceof L.LayerGroup)}))),e&&(r=(r=(r=r.filter((function(t){return!!t.pm}))).filter((function(t){return!t._pmTempLayer}))).filter((function(t){return!L.PM.optIn&&!t.options.pmIgnore||L.PM.optIn&&!1===t.options.pmIgnore}))),r},setOptions:function(t){var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:[];0===e.length&&(this._layers=this.getLayers()),this.options=t,this._layers.forEach((function(n){n.pm&&(n instanceof L.LayerGroup?-1===e.indexOf(n._leaflet_id)&&(e.push(n._leaflet_id),n.pm.setOptions(t,e)):n.pm.setOptions(t))}))}}),be.Marker=be.extend({_shape:"Marker",initialize:function(t){this._layer=t,this._enabled=!1,this._layer.on("dragend",this._onDragEnd,this)},enable:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{draggable:!0};L.Util.setOptions(this,t),this.options.allowEditing&&this._layer._map?(this._map=this._layer._map,this.enabled()&&this.disable(),this.applyOptions(),this._layer.on("remove",this.disable,this),this._enabled=!0,this._fireEnable()):this.disable()},disable:function(){this.enabled()&&(this.disableLayerDrag(),this._layer.off("remove",this.disable,this),this._layer.off("contextmenu",this._removeMarker,this),this._layerEdited&&this._fireUpdate(),this._layerEdited=!1,this._fireDisable(),this._enabled=!1)},enabled:function(){return this._enabled},toggleEdit:function(t){this.enabled()?this.disable():this.enable(t)},applyOptions:function(){this.options.snappable?this._initSnappableMarkers():this._disableSnapping(),this.options.draggable?this.enableLayerDrag():this.disableLayerDrag(),this.options.preventMarkerRemoval||this._layer.on("contextmenu",this._removeMarker,this)},_removeMarker:function(t){var e=t.target;e.remove(),this._fireRemove(e),this._fireRemove(this._map,e)},_onDragEnd:function(){this._fireEdit(),this._layerEdited=!0},_initSnappableMarkers:function(){var t=this._layer;this.options.snapDistance=this.options.snapDistance||30,this.options.snapSegment=this.options.snapSegment===undefined||this.options.snapSegment,t.off("pm:drag",this._handleSnapping,this),t.on("pm:drag",this._handleSnapping,this),t.off("pm:dragend",this._cleanupSnapping,this),t.on("pm:dragend",this._cleanupSnapping,this),t.off("pm:dragstart",this._unsnap,this),t.on("pm:dragstart",this._unsnap,this)},_disableSnapping:function(){var t=this._layer;t.off("pm:drag",this._handleSnapping,this),t.off("pm:dragend",this._cleanupSnapping,this),t.off("pm:dragstart",this._unsnap,this)}});const xe={filterMarkerGroup:function(){this.markerCache=[],this.createCache(),this._layer.on("pm:edit",this.createCache,this),this.applyLimitFilters({}),this._layer.on("pm:disable",this._removeMarkerLimitEvents,this),this.options.limitMarkersToCount>-1&&(this._layer.on("pm:vertexremoved",this._initMarkers,this),this._map.on("mousemove",this.applyLimitFilters,this))},_removeMarkerLimitEvents:function(){this._map.off("mousemove",this.applyLimitFilters,this),this._layer.off("pm:edit",this.createCache,this),this._layer.off("pm:disable",this._removeMarkerLimitEvents,this),this._layer.off("pm:vertexremoved",this._initMarkers,this)},createCache:function(){var t=[].concat(ke(this._markerGroup.getLayers()),ke(this.markerCache));this.markerCache=t.filter((function(t,e,n){return n.indexOf(t)===e}))},renderLimits:function(t){var e=this;this.markerCache.forEach((function(n){t.includes(n)?e._markerGroup.addLayer(n):e._markerGroup.removeLayer(n)}))},applyLimitFilters:function(t){var e=t.latlng,n=void 0===e?{lat:0,lng:0}:e;if(!this._preventRenderMarkers){var i=ke(this._filterClosestMarkers(n));this.renderLimits(i)}},_filterClosestMarkers:function(t){var e=ke(this.markerCache),n=this.options.limitMarkersToCount;return e.sort((function(e,n){return e._latlng.distanceTo(t)-n._latlng.distanceTo(t)})),e.filter((function(t,e){return!(n>-1)||et.length)&&(e=t.length);for(var n=0,i=new Array(e);n1?B()(r,l):r,u=o.length>1?B()(this._markers,l):this._markers;h.splice(s+1,0,i),u.splice(s+1,0,t),this._layer.setLatLngs(r),!0!==this.options.hideMiddleMarkers&&(this._createMiddleMarker(e,t),this._createMiddleMarker(t,n)),this._fireEdit(),this._layerEdited=!0,this._fireVertexAdded(t,L.PM.Utils.findDeepMarkerIndex(this._markers,t).indexPath,i),this.options.snappable&&this._initSnappableMarkers()},hasSelfIntersection:function(){return _t(this._layer.toGeoJSON(15)).features.length>0},_handleSelfIntersectionOnVertexRemoval:function(){this._handleLayerStyle(!0),this.hasSelfIntersection()&&(this._layer.setLatLngs(this._coordsBeforeEdit),this._coordsBeforeEdit=null,this._initMarkers())},_handleLayerStyle:function(t){var e=this._layer;if(this.hasSelfIntersection()){if(!this.options.allowSelfIntersection&&this.options.allowSelfIntersectionEdit&&this._updateDisabledMarkerStyle(this._markers,!0),this.isRed)return;t?this._flashLayer():(e.setStyle({color:"#f00000ff"}),this.isRed=!0),this._fireIntersect(_t(this._layer.toGeoJSON(15)))}else e.setStyle({color:this.cachedColor}),this.isRed=!1,!this.options.allowSelfIntersection&&this.options.allowSelfIntersectionEdit&&this._updateDisabledMarkerStyle(this._markers,!1)},_flashLayer:function(){var t=this;this.cachedColor||(this.cachedColor=this._layer.options.color),this._layer.setStyle({color:"#f00000ff"}),this.isRed=!0,window.setTimeout((function(){t._layer.setStyle({color:t.cachedColor}),t.isRed=!1}),200)},_updateDisabledMarkerStyle:function(t,e){var n=this;t.forEach((function(t){Array.isArray(t)?n._updateDisabledMarkerStyle(t,e):t._icon&&(e&&!n._checkMarkerAllowedToDrag(t)?L.DomUtil.addClass(t._icon,"vertexmarker-disabled"):L.DomUtil.removeClass(t._icon,"vertexmarker-disabled"))}))},_removeMarker:function(t){var e=t.target;if(this._vertexValidation("remove",t)){if(!this.options.allowSelfIntersection){var n=this._layer.getLatLngs();this._coordsBeforeEdit=JSON.parse(JSON.stringify(n))}var i=this._layer.getLatLngs(),r=L.PM.Utils.findDeepMarkerIndex(this._markers,e),a=r.indexPath,o=r.index,s=r.parentPath;if(a){var l=a.length>1?B()(i,s):i,h=a.length>1?B()(this._markers,s):this._markers;if(this.options.removeLayerBelowMinVertexCount||!(l.length<=2||this.isPolygon()&&l.length<=3)){l.splice(o,1),this._layer.setLatLngs(i),this.isPolygon()&&l.length<=2&&l.splice(0,l.length);var u=!1;if(l.length<=1&&(l.splice(0,l.length),this._layer.setLatLngs(i),this.disable(),this.enable(this.options),u=!0),G(i)&&this._layer.remove(),i=A(i),this._layer.setLatLngs(i),this._markers=A(this._markers),!u&&(h=a.length>1?B()(this._markers,s):this._markers,e._middleMarkerPrev&&this._markerGroup.removeLayer(e._middleMarkerPrev),e._middleMarkerNext&&this._markerGroup.removeLayer(e._middleMarkerNext),this._markerGroup.removeLayer(e),h)){var c,p;if(this.isPolygon()?(c=(o+1)%h.length,p=(o+(h.length-1))%h.length):(p=o-1<0?undefined:o-1,c=o+1>=h.length?undefined:o+1),c!==p){var d=h[p],f=h[c];!0!==this.options.hideMiddleMarkers&&this._createMiddleMarker(d,f)}h.splice(o,1)}this._fireEdit(),this._layerEdited=!0,this._fireVertexRemoved(e,a)}else this._flashLayer()}}},updatePolygonCoordsFromMarkerDrag:function(t){var e=this._layer.getLatLngs(),n=t.getLatLng(),i=L.PM.Utils.findDeepMarkerIndex(this._markers,t),r=i.indexPath,a=i.index,o=i.parentPath;(r.length>1?B()(e,o):e).splice(a,1,n),this._layer.setLatLngs(e)},_getNeighborMarkers:function(t){var e=L.PM.Utils.findDeepMarkerIndex(this._markers,t),n=e.indexPath,i=e.index,r=e.parentPath,a=n.length>1?B()(this._markers,r):this._markers,o=(i+1)%a.length;return{prevMarker:a[(i+(a.length-1))%a.length],nextMarker:a[o]}},_checkMarkerAllowedToDrag:function(t){var e=this._getNeighborMarkers(t),n=e.prevMarker,i=e.nextMarker,r=L.polyline([n.getLatLng(),t.getLatLng()]),a=L.polyline([t.getLatLng(),i.getLatLng()]),o=It(this._layer.toGeoJSON(15),r.toGeoJSON(15)).features.length,s=It(this._layer.toGeoJSON(15),a.toGeoJSON(15)).features.length;return t.getLatLng()===this._markers[0][0].getLatLng()?s+=1:t.getLatLng()===this._markers[0][this._markers[0].length-1].getLatLng()&&(o+=1),!(o<=2&&s<=2)},_onMarkerDragStart:function(t){var e=t.target;if(this.cachedColor||(this.cachedColor=this._layer.options.color),this._vertexValidation("move",t)){var n=L.PM.Utils.findDeepMarkerIndex(this._markers,e).indexPath;this._fireMarkerDragStart(t,n),this.options.allowSelfIntersection||(this._coordsBeforeEdit=this._layer.getLatLngs()),!this.options.allowSelfIntersection&&this.options.allowSelfIntersectionEdit&&this.hasSelfIntersection()?this._markerAllowedToDrag=this._checkMarkerAllowedToDrag(e):this._markerAllowedToDrag=null}},_onMarkerDrag:function(t){var e=t.target;if(this._vertexValidationDrag(e)){var n=L.PM.Utils.findDeepMarkerIndex(this._markers,e),i=n.indexPath,r=n.index,a=n.parentPath;if(i){if(!this.options.allowSelfIntersection&&this.options.allowSelfIntersectionEdit&&this.hasSelfIntersection()&&!1===this._markerAllowedToDrag)return this._layer.setLatLngs(this._coordsBeforeEdit),this._initMarkers(),void this._handleLayerStyle();this.updatePolygonCoordsFromMarkerDrag(e);var o=i.length>1?B()(this._markers,a):this._markers,s=(r+1)%o.length,l=(r+(o.length-1))%o.length,h=e.getLatLng(),u=o[l].getLatLng(),c=o[s].getLatLng();if(e._middleMarkerNext){var p=L.PM.Utils.calcMiddleLatLng(this._map,h,c);e._middleMarkerNext.setLatLng(p)}if(e._middleMarkerPrev){var d=L.PM.Utils.calcMiddleLatLng(this._map,h,u);e._middleMarkerPrev.setLatLng(d)}this.options.allowSelfIntersection||this._handleLayerStyle(),this._fireMarkerDrag(t,i)}}},_onMarkerDragEnd:function(t){var e=t.target;if(this._vertexValidationDragEnd(e)){var n=L.PM.Utils.findDeepMarkerIndex(this._markers,e).indexPath,i=this.hasSelfIntersection();i&&this.options.allowSelfIntersectionEdit&&this._markerAllowedToDrag&&(i=!1);var r=!this.options.allowSelfIntersection&&i;if(this._fireMarkerDragEnd(t,n,r),r)return this._layer.setLatLngs(this._coordsBeforeEdit),this._coordsBeforeEdit=null,this._initMarkers(),this.options.snappable&&this._initSnappableMarkers(),this._handleLayerStyle(),void this._fireLayerReset(t,n);!this.options.allowSelfIntersection&&this.options.allowSelfIntersectionEdit&&this._handleLayerStyle(),this._fireEdit(),this._layerEdited=!0}},_onVertexClick:function(t){var e=t.target;if(!e._dragging){var n=L.PM.Utils.findDeepMarkerIndex(this._markers,e).indexPath;this._fireVertexClick(t,n)}}}),be.Polygon=be.Line.extend({_shape:"Polygon",_checkMarkerAllowedToDrag:function(t){var e=this._getNeighborMarkers(t),n=e.prevMarker,i=e.nextMarker,r=L.polyline([n.getLatLng(),t.getLatLng()]),a=L.polyline([t.getLatLng(),i.getLatLng()]),o=It(this._layer.toGeoJSON(15),r.toGeoJSON(15)).features.length,s=It(this._layer.toGeoJSON(15),a.toGeoJSON(15)).features.length;return!(o<=2&&s<=2)}}),be.Rectangle=be.Polygon.extend({_shape:"Rectangle",_initMarkers:function(){var t=this,e=this._map,n=this._findCorners();this._markerGroup&&this._markerGroup.clearLayers(),this._markerGroup=new L.LayerGroup,this._markerGroup._pmTempLayer=!0,e.addLayer(this._markerGroup),this._markers=[],this._markers[0]=n.map(this._createMarker,this);var i=we(this._markers,1);this._cornerMarkers=i[0],this._layer.getLatLngs()[0].forEach((function(e,n){var i=t._cornerMarkers.find((function(t){return t._index===n}));i&&i.setLatLng(e)}))},applyOptions:function(){this.options.snappable?this._initSnappableMarkers():this._disableSnapping(),this._addMarkerEvents()},_createMarker:function(t,e){var n=new L.Marker(t,{draggable:!0,icon:L.divIcon({className:"marker-icon"})});return this._setPane(n,"vertexPane"),n._origLatLng=t,n._index=e,n._pmTempLayer=!0,this._markerGroup.addLayer(n),n},_addMarkerEvents:function(){var t=this;this._markers[0].forEach((function(e){e.on("dragstart",t._onMarkerDragStart,t),e.on("drag",t._onMarkerDrag,t),e.on("dragend",t._onMarkerDragEnd,t),t.options.preventMarkerRemoval||e.on("contextmenu",t._removeMarker,t)}))},_removeMarker:function(){return null},_onMarkerDragStart:function(t){if(this._vertexValidation("move",t)){var e=t.target,n=this._cornerMarkers;e._oppositeCornerLatLng=n.find((function(t){return t._index===(e._index+2)%4})).getLatLng(),e._snapped=!1,this._fireMarkerDragStart(t)}},_onMarkerDrag:function(t){var e=t.target;this._vertexValidationDrag(e)&&e._index!==undefined&&(this._adjustRectangleForMarkerMove(e),this._fireMarkerDrag(t))},_onMarkerDragEnd:function(t){var e=t.target;this._vertexValidationDragEnd(e)&&(this._cornerMarkers.forEach((function(t){delete t._oppositeCornerLatLng})),this._fireMarkerDragEnd(t),this._fireEdit(),this._layerEdited=!0)},_adjustRectangleForMarkerMove:function(t){L.extend(t._origLatLng,t._latlng);var e=L.PM.Utils._getRotatedRectangle(t.getLatLng(),t._oppositeCornerLatLng,this._angle||0,this._map);this._layer.setLatLngs(e),this._adjustAllMarkers(),this._layer.redraw()},_adjustAllMarkers:function(){var t=this,e=this._layer.getLatLngs()[0];e&&4!==e.length&&e.length>0?(e.forEach((function(e,n){t._cornerMarkers[n].setLatLng(e)})),this._cornerMarkers.slice(e.length).forEach((function(t){t.setLatLng(e[0])}))):e&&e.length?this._cornerMarkers.forEach((function(t){t.setLatLng(e[t._index])})):console.error("The layer has no LatLngs")},_findCorners:function(){var t=this._layer.getLatLngs()[0];return L.PM.Utils._getRotatedRectangle(t[0],t[2],this._angle||0,this._map)}}),be.Circle=be.extend({_shape:"Circle",initialize:function(t){this._layer=t,this._enabled=!1,this._updateHiddenPolyCircle()},enable:function(t){L.Util.setOptions(this,t),this._map=this._layer._map,this.options.allowEditing?(this.enabled()||this.disable(),this._enabled=!0,this._initMarkers(),this.applyOptions(),this._layer.on("remove",this.disable,this),this._updateHiddenPolyCircle(),this._fireEnable()):this.disable()},disable:function(){if(this.enabled()&&!this._dragging){this._centerMarker.off("dragstart",this._onCircleDragStart,this),this._centerMarker.off("drag",this._onCircleDrag,this),this._centerMarker.off("dragend",this._onCircleDragEnd,this),this._outerMarker.off("drag",this._handleOuterMarkerSnapping,this),this._layer.off("remove",this.disable,this),this._enabled=!1,this._helperLayers.clearLayers();var t=this._layer._path?this._layer._path:this._layer._renderer._container;L.DomUtil.removeClass(t,"leaflet-pm-draggable"),this._layerEdited&&this._fireUpdate(),this._layerEdited=!1,this._fireDisable()}},enabled:function(){return this._enabled},toggleEdit:function(t){this.enabled()?this.disable():this.enable(t)},_initMarkers:function(){var t=this._map;this._helperLayers&&this._helperLayers.clearLayers(),this._helperLayers=new L.LayerGroup,this._helperLayers._pmTempLayer=!0,this._helperLayers.addTo(t);var e=this._layer.getLatLng(),n=this._layer._radius,i=this._getLatLngOnCircle(e,n);this._centerMarker=this._createCenterMarker(e),this._outerMarker=this._createOuterMarker(i),this._markers=[this._centerMarker,this._outerMarker],this._createHintLine(this._centerMarker,this._outerMarker)},applyOptions:function(){this.options.snappable?(this._initSnappableMarkers(),this._outerMarker.on("drag",this._handleOuterMarkerSnapping,this),this._outerMarker.on("move",this._syncHintLine,this),this._outerMarker.on("move",this._syncCircleRadius,this),this._centerMarker.on("move",this._moveCircle,this)):this._disableSnapping()},_createHintLine:function(t,e){var n=t.getLatLng(),i=e.getLatLng();this._hintline=L.polyline([n,i],this.options.hintlineStyle),this._setPane(this._hintline,"layerPane"),this._hintline._pmTempLayer=!0,this._helperLayers.addLayer(this._hintline)},_createCenterMarker:function(t){var e=this._createMarker(t);return L.DomUtil.addClass(e._icon,"leaflet-pm-draggable"),e.on("drag",this._moveCircle,this),e.on("dragstart",this._onCircleDragStart,this),e.on("drag",this._onCircleDrag,this),e.on("dragend",this._onCircleDragEnd,this),e},_createOuterMarker:function(t){var e=this._createMarker(t);return e.on("drag",this._resizeCircle,this),e},_createMarker:function(t){var e=new L.Marker(t,{draggable:!0,icon:L.divIcon({className:"marker-icon"})});return this._setPane(e,"vertexPane"),e._origLatLng=t,e._pmTempLayer=!0,e.on("dragstart",this._onMarkerDragStart,this),e.on("drag",this._onMarkerDrag,this),e.on("dragend",this._onMarkerDragEnd,this),this._helperLayers.addLayer(e),e},_resizeCircle:function(){this._outerMarker.setLatLng(this._getNewDestinationOfOuterMarker()),this._syncHintLine(),this._syncCircleRadius()},_moveCircle:function(t){if(!t.target._cancelDragEventChain){var e=t.latlng;this._layer.setLatLng(e);var n=this._layer._radius,i=this._getLatLngOnCircle(e,n);this._outerMarker._latlng=i,this._outerMarker.update(),this._syncHintLine(),this._updateHiddenPolyCircle(),this._fireCenterPlaced("Edit")}},_syncCircleRadius:function(){var t=this._centerMarker.getLatLng(),e=this._outerMarker.getLatLng(),n=this._map.distance(t,e);this.options.minRadiusCircle&&nthis.options.maxRadiusCircle?this._layer.setRadius(this.options.maxRadiusCircle):this._layer.setRadius(n),this._updateHiddenPolyCircle()},_syncHintLine:function(){var t=this._centerMarker.getLatLng(),e=this._outerMarker.getLatLng();this._hintline.setLatLngs([t,e])},_disableSnapping:function(){var t=this;this._markers.forEach((function(e){e.off("move",t._syncHintLine,t),e.off("move",t._syncCircleRadius,t),e.off("drag",t._handleSnapping,t),e.off("dragend",t._cleanupSnapping,t)})),this._layer.off("pm:dragstart",this._unsnap,this)},_onMarkerDragStart:function(t){this._vertexValidation("move",t)&&this._fireMarkerDragStart(t)},_onMarkerDrag:function(t){var e=t.target;this._vertexValidationDrag(e)&&this._fireMarkerDrag(t)},_onMarkerDragEnd:function(t){var e=t.target;this._vertexValidationDragEnd(e)&&(this._fireEdit(),this._layerEdited=!0,this._fireMarkerDragEnd(t))},_onCircleDragStart:function(t){this._vertexValidationDrag(t.target)?(delete this._vertexValidationReset,this._fireDragStart(t)):this._vertexValidationReset=!0},_onCircleDrag:function(t){this._vertexValidationReset||this._fireDrag(t)},_onCircleDragEnd:function(){this._vertexValidationReset?delete this._vertexValidationReset:this._fireDragEnd()},_updateHiddenPolyCircle:function(){var t=this._map&&this._map.pm._isCRSSimple();this._hiddenPolyCircle?this._hiddenPolyCircle.setLatLngs(L.PM.Utils.circleToPolygon(this._layer,200,!t).getLatLngs()):this._hiddenPolyCircle=L.PM.Utils.circleToPolygon(this._layer,200,!t),this._hiddenPolyCircle._parentCopy||(this._hiddenPolyCircle._parentCopy=this._layer)},_getLatLngOnCircle:function(t,e){var n=this._map.project(t),i=L.point(n.x+e,n.y);return this._map.unproject(i)},_getNewDestinationOfOuterMarker:function(){var t=this._centerMarker.getLatLng(),e=this._outerMarker.getLatLng(),n=this._map.distance(t,e);return this.options.minRadiusCircle&&nthis.options.maxRadiusCircle&&(e=U(this._map,t,e,this.options.maxRadiusCircle)),e},_handleOuterMarkerSnapping:function(){if(this._outerMarker._snapped){var t=this._centerMarker.getLatLng(),e=this._outerMarker.getLatLng(),n=this._map.distance(t,e);(this.options.minRadiusCircle&&nthis.options.maxRadiusCircle)&&this._outerMarker.setLatLng(this._outerMarker._orgLatLng)}this._outerMarker.setLatLng(this._getNewDestinationOfOuterMarker())}}),be.CircleMarker=be.extend({_shape:"CircleMarker",initialize:function(t){this._layer=t,this._enabled=!1,this._updateHiddenPolyCircle()},enable:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{draggable:!0,snappable:!0};L.Util.setOptions(this,t),this.options.allowEditing&&this._layer._map?(this._map=this._layer._map,this.enabled()&&this.disable(),this.applyOptions(),this._layer.on("remove",this.disable,this),this._enabled=!0,this._layer.on("pm:dragstart",this._onDragStart,this),this._layer.on("pm:drag",this._onMarkerDrag,this),this._layer.on("pm:dragend",this._onMarkerDragEnd,this),this._updateHiddenPolyCircle(),this._fireEnable()):this.disable()},disable:function(){this._dragging||(this._helperLayers&&this._helperLayers.clearLayers(),this._map||(this._map=this._layer._map),this._map||(this.options.editable?(this._map.off("move",this._syncMarkers,this),this._outerMarker&&this._outerMarker.on("drag",this._handleOuterMarkerSnapping,this)):this._map.off("move",this._updateHiddenPolyCircle,this)),this.disableLayerDrag(),this._layer.off("contextmenu",this._removeMarker,this),this._layer.off("remove",this.disable,this),this.enabled()&&(this._layerEdited&&this._fireUpdate(),this._layerEdited=!1,this._fireDisable()),this._enabled=!1)},enabled:function(){return this._enabled},toggleEdit:function(t){this.enabled()?this.disable():this.enable(t)},applyOptions:function(){!this.options.editable&&this.options.draggable?this.enableLayerDrag():this.disableLayerDrag(),this.options.editable?(this._initMarkers(),this._map.on("move",this._syncMarkers,this)):this._map.on("move",this._updateHiddenPolyCircle,this),this.options.snappable?this.options.editable?(this._initSnappableMarkers(),this._centerMarker.on("drag",this._moveCircle,this),this.options.editable&&this._outerMarker.on("drag",this._handleOuterMarkerSnapping,this),this._outerMarker.on("move",this._syncHintLine,this),this._outerMarker.on("move",this._syncCircleRadius,this)):this._initSnappableMarkersDrag():this.options.editable?this._disableSnapping():this._disableSnappingDrag(),this.options.preventMarkerRemoval||this._layer.on("contextmenu",this._removeMarker,this)},_initMarkers:function(){var t=this._map;this._helperLayers&&this._helperLayers.clearLayers(),this._helperLayers=new L.LayerGroup,this._helperLayers._pmTempLayer=!0,this._helperLayers.addTo(t);var e=this._layer.getLatLng(),n=this._layer._radius,i=this._getLatLngOnCircle(e,n);this._centerMarker=this._createCenterMarker(e),this._outerMarker=this._createOuterMarker(i),this._markers=[this._centerMarker,this._outerMarker],this._createHintLine(this._centerMarker,this._outerMarker)},_getLatLngOnCircle:function(t,e){var n=this._map.project(t),i=L.point(n.x+e,n.y);return this._map.unproject(i)},_createHintLine:function(t,e){var n=t.getLatLng(),i=e.getLatLng();this._hintline=L.polyline([n,i],this.options.hintlineStyle),this._setPane(this._hintline,"layerPane"),this._hintline._pmTempLayer=!0,this._helperLayers.addLayer(this._hintline)},_createCenterMarker:function(t){var e=this._createMarker(t);return this.options.draggable?L.DomUtil.addClass(e._icon,"leaflet-pm-draggable"):e.dragging.disable(),e},_createOuterMarker:function(t){var e=this._createMarker(t);return e.on("drag",this._resizeCircle,this),e},_createMarker:function(t){var e=new L.Marker(t,{draggable:!0,icon:L.divIcon({className:"marker-icon"})});return this._setPane(e,"vertexPane"),e._origLatLng=t,e._pmTempLayer=!0,e.on("dragstart",this._onMarkerDragStart,this),e.on("drag",this._onMarkerDrag,this),e.on("dragend",this._onMarkerDragEnd,this),this._helperLayers.addLayer(e),e},_moveCircle:function(){var t=this._centerMarker.getLatLng();this._layer.setLatLng(t);var e=this._layer._radius,n=this._getLatLngOnCircle(t,e);this._outerMarker._latlng=n,this._outerMarker.update(),this._syncHintLine(),this._updateHiddenPolyCircle(),this._fireCenterPlaced("Edit")},_syncMarkers:function(){var t=this._layer.getLatLng(),e=this._layer._radius,n=this._getLatLngOnCircle(t,e);this._outerMarker.setLatLng(n),this._centerMarker.setLatLng(t),this._syncHintLine(),this._updateHiddenPolyCircle()},_resizeCircle:function(){this._outerMarker.setLatLng(this._getNewDestinationOfOuterMarker()),this._syncHintLine(),this._syncCircleRadius()},_syncCircleRadius:function(){var t=this._centerMarker.getLatLng(),e=this._outerMarker.getLatLng(),n=this._map.project(t).distanceTo(this._map.project(e));this.options.minRadiusCircleMarker&&nthis.options.maxRadiusCircleMarker?this._layer.setRadius(this.options.maxRadiusCircleMarker):this._layer.setRadius(n),this._updateHiddenPolyCircle()},_syncHintLine:function(){var t=this._centerMarker.getLatLng(),e=this._outerMarker.getLatLng();this._hintline.setLatLngs([t,e])},_removeMarker:function(){this.options.editable&&this.disable(),this._layer.remove(),this._fireRemove(this._layer),this._fireRemove(this._map,this._layer)},_onDragStart:function(){this._map.pm.Draw.CircleMarker._layerIsDragging=!0},_onMarkerDragStart:function(t){this._vertexValidation("move",t)&&this._fireMarkerDragStart(t)},_onMarkerDrag:function(t){var e=t.target;e instanceof L.Marker&&!this._vertexValidationDrag(e)||this._fireMarkerDrag(t)},_onMarkerDragEnd:function(t){this._map.pm.Draw.CircleMarker._layerIsDragging=!1;var e=t.target;this._vertexValidationDragEnd(e)&&(this.options.editable&&(this._fireEdit(),this._layerEdited=!0),this._fireMarkerDragEnd(t))},_initSnappableMarkersDrag:function(){var t=this._layer;this.options.snapDistance=this.options.snapDistance||30,this.options.snapSegment=this.options.snapSegment===undefined||this.options.snapSegment,t.off("pm:drag",this._handleSnapping,this),t.on("pm:drag",this._handleSnapping,this),t.off("pm:dragend",this._cleanupSnapping,this),t.on("pm:dragend",this._cleanupSnapping,this),t.off("pm:dragstart",this._unsnap,this),t.on("pm:dragstart",this._unsnap,this)},_disableSnappingDrag:function(){var t=this._layer;t.off("pm:drag",this._handleSnapping,this),t.off("pm:dragend",this._cleanupSnapping,this),t.off("pm:dragstart",this._unsnap,this)},_updateHiddenPolyCircle:function(){var t=this._layer._map||this._map;if(t){var e=L.PM.Utils.pxRadiusToMeterRadius(this._layer.getRadius(),t,this._layer.getLatLng()),n=L.circle(this._layer.getLatLng(),this._layer.options);n.setRadius(e);var i=t&&t.pm._isCRSSimple();this._hiddenPolyCircle?this._hiddenPolyCircle.setLatLngs(L.PM.Utils.circleToPolygon(n,200,!i).getLatLngs()):this._hiddenPolyCircle=L.PM.Utils.circleToPolygon(n,200,!i),this._hiddenPolyCircle._parentCopy||(this._hiddenPolyCircle._parentCopy=this._layer)}},_getNewDestinationOfOuterMarker:function(){var t=this._centerMarker.getLatLng(),e=this._outerMarker.getLatLng(),n=this._map.project(t).distanceTo(this._map.project(e));return this.options.minRadiusCircleMarker&&nthis.options.maxRadiusCircleMarker&&(e=U(this._map,t,e,L.PM.Utils.pxRadiusToMeterRadius(this.options.maxRadiusCircleMarker,this._map,t))),e},_handleOuterMarkerSnapping:function(){if(this._outerMarker._snapped){var t=this._centerMarker.getLatLng(),e=this._outerMarker.getLatLng(),n=this._map.project(t).distanceTo(this._map.project(e));(this.options.minRadiusCircleMarker&&nthis.options.maxRadiusCircleMarker)&&this._outerMarker.setLatLng(this._outerMarker._orgLatLng)}this._outerMarker.setLatLng(this._getNewDestinationOfOuterMarker())}}),be.ImageOverlay=be.extend({_shape:"ImageOverlay",initialize:function(t){this._layer=t,this._enabled=!1},toggleEdit:function(t){this.enabled()?this.disable():this.enable(t)},enabled:function(){return this._enabled},enable:function(){var t=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{draggable:!0,snappable:!0};L.Util.setOptions(this,t),this._map=this._layer._map,this._map&&(this.options.allowEditing?(this.enabled()||this.disable(),this.enableLayerDrag(),this._layer.on("remove",this.disable,this),this._enabled=!0,this._otherSnapLayers=this._findCorners(),this._fireEnable()):this.disable())},disable:function(){this._dragging||(this._map||(this._map=this._layer._map),this.disableLayerDrag(),this._layer.off("remove",this.disable,this),this.enabled()||(this._layerEdited&&this._fireUpdate(),this._layerEdited=!1,this._fireDisable()),this._enabled=!1)},_findCorners:function(){var t=this._layer.getBounds();return[t.getNorthWest(),t.getNorthEast(),t.getSouthEast(),t.getSouthWest()]}});var Pe=function(t,e,n,i,r,a){this._matrix=[t,e,n,i,r,a]};Pe.init=function(){return new L.PM.Matrix(1,0,0,1,0,0)},Pe.prototype={transform:function(t){return this._transform(t.clone())},_transform:function(t){var e=this._matrix,n=t.x,i=t.y;return t.x=e[0]*n+e[1]*i+e[4],t.y=e[2]*n+e[3]*i+e[5],t},untransform:function(t){var e=this._matrix;return new L.Point((t.x/e[0]-e[4])/e[0],(t.y/e[2]-e[5])/e[2])},clone:function(){var t=this._matrix;return new L.PM.Matrix(t[0],t[1],t[2],t[3],t[4],t[5])},translate:function(t){return t===undefined?new L.Point(this._matrix[4],this._matrix[5]):("number"==typeof t?(e=t,n=t):(e=t.x,n=t.y),this._add(1,0,0,1,e,n));var e,n},scale:function(t,e){return t===undefined?new L.Point(this._matrix[0],this._matrix[3]):(e=e||L.point(0,0),"number"==typeof t?(n=t,i=t):(n=t.x,i=t.y),this._add(n,0,0,i,e.x,e.y)._add(1,0,0,1,-e.x,-e.y));var n,i},rotate:function(t,e){var n=Math.cos(t),i=Math.sin(t);return e=e||new L.Point(0,0),this._add(n,i,-i,n,e.x,e.y)._add(1,0,0,1,-e.x,-e.y)},flip:function(){return this._matrix[1]*=-1,this._matrix[2]*=-1,this},_add:function(t,e,n,i,r,a){var o,s=[[],[],[]],l=this._matrix,h=[[l[0],l[2],l[4]],[l[1],l[3],l[5]],[0,0,1]],u=[[t,n,r],[e,i,a],[0,0,1]];t&&t instanceof L.PM.Matrix&&(u=[[(l=t._matrix)[0],l[2],l[4]],[l[1],l[3],l[5]],[0,0,1]]);for(var c=0;c<3;c+=1)for(var p=0;p<3;p+=1){o=0;for(var d=0;d<3;d+=1)o+=h[c][d]*u[d][p];s[c][p]=o}return this._matrix=[s[0][0],s[1][0],s[0][1],s[1][1],s[0][2],s[1][2]],this}};const Ee=Pe;var Se={calcMiddleLatLng:function(t,e,n){var i=t.project(e),r=t.project(n);return t.unproject(i._add(r)._divideBy(2))},findLayers:function(t){var e=[];return t.eachLayer((function(t){(t instanceof L.Polyline||t instanceof L.Marker||t instanceof L.Circle||t instanceof L.CircleMarker||t instanceof L.ImageOverlay)&&e.push(t)})),e=(e=(e=e.filter((function(t){return!!t.pm}))).filter((function(t){return!t._pmTempLayer}))).filter((function(t){return!L.PM.optIn&&!t.options.pmIgnore||L.PM.optIn&&!1===t.options.pmIgnore}))},circleToPolygon:function(t){for(var e=arguments.length>1&&arguments[1]!==undefined?arguments[1]:60,n=!(arguments.length>2&&arguments[2]!==undefined)||arguments[2],i=t.getLatLng(),r=t.getRadius(),a=z(i,r,e,0,n),o=[],s=0;s3&&arguments[3]!==undefined&&arguments[3];t.fire(e,n,i);var r=this.getAllParentGroups(t),a=r.groups;a.forEach((function(t){t.fire(e,n,i)}))},getAllParentGroups:function(t){var e=[],n=[];return!t._pmLastGroupFetch||!t._pmLastGroupFetch.time||(new Date).getTime()-t._pmLastGroupFetch.time>1e3?(function i(t){for(var r in t._eventParents)if(-1===e.indexOf(r)){e.push(r);var a=t._eventParents[r];n.push(a),i(a)}}(t),t._pmLastGroupFetch={time:(new Date).getTime(),groups:n,groupIds:e},{groupIds:e,groups:n}):{groups:t._pmLastGroupFetch.groups,groupIds:t._pmLastGroupFetch.groupIds}},createGeodesicPolygon:z,getTranslation:j,findDeepCoordIndex:function(t,e){var n;t.some(function r(t){return function(i,a){var o=t.concat(a);return i.lat&&i.lat===e.lat&&i.lng===e.lng?(n=o,!0):Array.isArray(i)&&i.some(r(o))}}([]));var i={};return n&&(i={indexPath:n,index:n[n.length-1],parentPath:n.slice(0,n.length-1)}),i},findDeepMarkerIndex:function(t,e){var n;t.some(function r(t){return function(i,a){var o=t.concat(a);return i._leaflet_id===e._leaflet_id?(n=o,!0):Array.isArray(i)&&i.some(r(o))}}([]));var i={};return n&&(i={indexPath:n,index:n[n.length-1],parentPath:n.slice(0,n.length-1)}),i},_getIndexFromSegment:function(t,e){if(e&&2===e.length){var n=this.findDeepCoordIndex(t,e[0]),i=this.findDeepCoordIndex(t,e[1]),r=Math.max(n.index,i.index);return 0!==n.index&&0!==i.index||1===r||(r+=1),{indexA:n,indexB:i,newIndex:r,indexPath:n.indexPath,parentPath:n.parentPath}}return null},_getRotatedRectangle:function(t,e,n,i){var r=me(i,t),a=me(i,e),o=n*Math.PI/180,s=Math.cos(o),l=Math.sin(o),h=(a.x-r.x)*s+(a.y-r.y)*l,u=(a.y-r.y)*s-(a.x-r.x)*l,c=h*s+r.x,p=h*l+r.y,d=-u*l+r.x,f=u*s+r.y;return[ye(i,r),ye(i,{x:c,y:p}),ye(i,a),ye(i,{x:d,y:f})]},pxRadiusToMeterRadius:function(t,e,n){var i=e.project(n),r=L.point(i.x+t,i.y);return e.distance(e.unproject(r),n)}};const Oe=Se;L.PM=L.PM||{version:"2.11.4",Map:D,Toolbar:W,Draw:rt,Edit:be,Utils:Oe,Matrix:Ee,activeLang:"en",optIn:!1,initialize:function(t){this.addInitHooks(t)},setOptIn:function(t){this.optIn=!!t},addInitHooks:function(){L.Map.addInitHook((function(){this.pm=undefined,L.PM.optIn?!1===this.options.pmIgnore&&(this.pm=new L.PM.Map(this)):this.options.pmIgnore||(this.pm=new L.PM.Map(this))})),L.LayerGroup.addInitHook((function(){this.pm=undefined,L.PM.optIn?!1===this.options.pmIgnore&&(this.pm=new L.PM.Edit.LayerGroup(this)):this.options.pmIgnore||(this.pm=new L.PM.Edit.LayerGroup(this))})),L.Marker.addInitHook((function(){this.pm=undefined,L.PM.optIn?!1===this.options.pmIgnore&&(this.pm=new L.PM.Edit.Marker(this)):this.options.pmIgnore||(this.pm=new L.PM.Edit.Marker(this))})),L.CircleMarker.addInitHook((function(){this.pm=undefined,L.PM.optIn?!1===this.options.pmIgnore&&(this.pm=new L.PM.Edit.CircleMarker(this)):this.options.pmIgnore||(this.pm=new L.PM.Edit.CircleMarker(this))})),L.Polyline.addInitHook((function(){this.pm=undefined,L.PM.optIn?!1===this.options.pmIgnore&&(this.pm=new L.PM.Edit.Line(this)):this.options.pmIgnore||(this.pm=new L.PM.Edit.Line(this))})),L.Polygon.addInitHook((function(){this.pm=undefined,L.PM.optIn?!1===this.options.pmIgnore&&(this.pm=new L.PM.Edit.Polygon(this)):this.options.pmIgnore||(this.pm=new L.PM.Edit.Polygon(this))})),L.Rectangle.addInitHook((function(){this.pm=undefined,L.PM.optIn?!1===this.options.pmIgnore&&(this.pm=new L.PM.Edit.Rectangle(this)):this.options.pmIgnore||(this.pm=new L.PM.Edit.Rectangle(this))})),L.Circle.addInitHook((function(){this.pm=undefined,L.PM.optIn?!1===this.options.pmIgnore&&(this.pm=new L.PM.Edit.Circle(this)):this.options.pmIgnore||(this.pm=new L.PM.Edit.Circle(this))})),L.ImageOverlay.addInitHook((function(){this.pm=undefined,L.PM.optIn?!1===this.options.pmIgnore&&(this.pm=new L.PM.Edit.ImageOverlay(this)):this.options.pmIgnore||(this.pm=new L.PM.Edit.ImageOverlay(this))}))},reInitLayer:function(t){var e=this;t instanceof L.LayerGroup&&t.eachLayer((function(t){e.reInitLayer(t)})),t.pm||L.PM.optIn&&!1!==t.options.pmIgnore||t.options.pmIgnore||(t instanceof L.Map?t.pm=new L.PM.Map(t):t instanceof L.Marker?t.pm=new L.PM.Edit.Marker(t):t instanceof L.Circle?t.pm=new L.PM.Edit.Circle(t):t instanceof L.CircleMarker?t.pm=new L.PM.Edit.CircleMarker(t):t instanceof L.Rectangle?t.pm=new L.PM.Edit.Rectangle(t):t instanceof L.Polygon?t.pm=new L.PM.Edit.Polygon(t):t instanceof L.Polyline?t.pm=new L.PM.Edit.Line(t):t instanceof L.LayerGroup?t.pm=new L.PM.Edit.LayerGroup(t):t instanceof L.ImageOverlay&&(t.pm=new L.PM.Edit.ImageOverlay(t)))}},"1.7.1"===L.version&&L.Canvas.include({_onClick:function(t){for(var e,n,i=this._map.mouseEventToLayerPoint(t),r=this._drawFirst;r;r=r.next)(e=r.layer).options.interactive&&e._containsPoint(i)&&("click"!==t.type&&"preclick"!==t.type||!this._map._draggableMoved(e))&&(n=e);n&&(L.DomEvent.fakeStop(t),this._fireEvent([n],t))}}),L.PM.initialize()},7107:()=>{Array.prototype.findIndex=Array.prototype.findIndex||function(t){if(null===this)throw new TypeError("Array.prototype.findIndex called on null or undefined");if("function"!=typeof t)throw new TypeError("callback must be a function");for(var e=Object(this),n=e.length>>>0,i=arguments[1],r=0;r>>0,i=arguments[1],r=0;r>>0;if(0===i)return!1;var r,a,o=0|e,s=Math.max(o>=0?o:i-Math.abs(o),0);for(;s{var i=n(2582),r=n(4102),a=n(1540),o=n(9705).Z,s=a.featureEach,l=(a.coordEach,r.polygon,r.featureCollection);function h(t){var e=new i(t);return e.insert=function(t){if("Feature"!==t.type)throw new Error("invalid feature");return t.bbox=t.bbox?t.bbox:o(t),i.prototype.insert.call(this,t)},e.load=function(t){var e=[];return Array.isArray(t)?t.forEach((function(t){if("Feature"!==t.type)throw new Error("invalid features");t.bbox=t.bbox?t.bbox:o(t),e.push(t)})):s(t,(function(t){if("Feature"!==t.type)throw new Error("invalid features");t.bbox=t.bbox?t.bbox:o(t),e.push(t)})),i.prototype.load.call(this,e)},e.remove=function(t,e){if("Feature"!==t.type)throw new Error("invalid feature");return t.bbox=t.bbox?t.bbox:o(t),i.prototype.remove.call(this,t,e)},e.clear=function(){return i.prototype.clear.call(this)},e.search=function(t){var e=i.prototype.search.call(this,this.toBBox(t));return l(e)},e.collides=function(t){return i.prototype.collides.call(this,this.toBBox(t))},e.all=function(){var t=i.prototype.all.call(this);return l(t)},e.toJSON=function(){return i.prototype.toJSON.call(this)},e.fromJSON=function(t){return i.prototype.fromJSON.call(this,t)},e.toBBox=function(t){var e;if(t.bbox)e=t.bbox;else if(Array.isArray(t)&&4===t.length)e=t;else if(Array.isArray(t)&&6===t.length)e=[t[0],t[1],t[3],t[4]];else if("Feature"===t.type)e=o(t);else{if("FeatureCollection"!==t.type)throw new Error("invalid geojson");e=o(t)}return{minX:e[0],minY:e[1],maxX:e[2],maxY:e[3]}},e}t.exports=h,t.exports["default"]=h},1989:(t,e,n)=>{var i=n(1789),r=n(401),a=n(7667),o=n(1327),s=n(1866);function l(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e{var i=n(7040),r=n(4125),a=n(2117),o=n(7518),s=n(4705);function l(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e{var i=n(852)(n(5639),"Map");t.exports=i},3369:(t,e,n)=>{var i=n(4785),r=n(1285),a=n(6e3),o=n(9916),s=n(5265);function l(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e{var i=n(8407),r=n(7465),a=n(3779),o=n(7599),s=n(4758),l=n(4309);function h(t){var e=this.__data__=new i(t);this.size=e.size}h.prototype.clear=r,h.prototype["delete"]=a,h.prototype.get=o,h.prototype.has=s,h.prototype.set=l,t.exports=h},2705:(t,e,n)=>{var i=n(5639).Symbol;t.exports=i},1149:(t,e,n)=>{var i=n(5639).Uint8Array;t.exports=i},6874:t=>{t.exports=function(t,e,n){switch(n.length){case 0:return t.call(e);case 1:return t.call(e,n[0]);case 2:return t.call(e,n[0],n[1]);case 3:return t.call(e,n[0],n[1],n[2])}return t.apply(e,n)}},4636:(t,e,n)=>{var i=n(2545),r=n(5694),a=n(1469),o=n(4144),s=n(5776),l=n(6719),h=Object.prototype.hasOwnProperty;t.exports=function(t,e){var n=a(t),u=!n&&r(t),c=!n&&!u&&o(t),p=!n&&!u&&!c&&l(t),d=n||u||c||p,f=d?i(t.length,String):[],g=f.length;for(var _ in t)!e&&!h.call(t,_)||d&&("length"==_||c&&("offset"==_||"parent"==_)||p&&("buffer"==_||"byteLength"==_||"byteOffset"==_)||s(_,g))||f.push(_);return f}},9932:t=>{t.exports=function(t,e){for(var n=-1,i=null==t?0:t.length,r=Array(i);++n{var i=n(9465),r=n(7813);t.exports=function(t,e,n){(n!==undefined&&!r(t[e],n)||n===undefined&&!(e in t))&&i(t,e,n)}},4865:(t,e,n)=>{var i=n(9465),r=n(7813),a=Object.prototype.hasOwnProperty;t.exports=function(t,e,n){var o=t[e];a.call(t,e)&&r(o,n)&&(n!==undefined||e in t)||i(t,e,n)}},8470:(t,e,n)=>{var i=n(7813);t.exports=function(t,e){for(var n=t.length;n--;)if(i(t[n][0],e))return n;return-1}},9465:(t,e,n)=>{var i=n(8777);t.exports=function(t,e,n){"__proto__"==e&&i?i(t,e,{configurable:!0,enumerable:!0,value:n,writable:!0}):t[e]=n}},3118:(t,e,n)=>{var i=n(3218),r=Object.create,a=function(){function t(){}return function(e){if(!i(e))return{};if(r)return r(e);t.prototype=e;var n=new t;return t.prototype=undefined,n}}();t.exports=a},8483:(t,e,n)=>{var i=n(5063)();t.exports=i},7786:(t,e,n)=>{var i=n(1811),r=n(327);t.exports=function(t,e){for(var n=0,a=(e=i(e,t)).length;null!=t&&n{var i=n(2705),r=n(9607),a=n(2333),o=i?i.toStringTag:undefined;t.exports=function(t){return null==t?t===undefined?"[object Undefined]":"[object Null]":o&&o in Object(t)?r(t):a(t)}},8565:t=>{var e=Object.prototype.hasOwnProperty;t.exports=function(t,n){return null!=t&&e.call(t,n)}},9454:(t,e,n)=>{var i=n(4239),r=n(7005);t.exports=function(t){return r(t)&&"[object Arguments]"==i(t)}},8458:(t,e,n)=>{var i=n(3560),r=n(5346),a=n(3218),o=n(346),s=/^\[object .+?Constructor\]$/,l=Function.prototype,h=Object.prototype,u=l.toString,c=h.hasOwnProperty,p=RegExp("^"+u.call(c).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");t.exports=function(t){return!(!a(t)||r(t))&&(i(t)?p:s).test(o(t))}},8749:(t,e,n)=>{var i=n(4239),r=n(1780),a=n(7005),o={};o["[object Float32Array]"]=o["[object Float64Array]"]=o["[object Int8Array]"]=o["[object Int16Array]"]=o["[object Int32Array]"]=o["[object Uint8Array]"]=o["[object Uint8ClampedArray]"]=o["[object Uint16Array]"]=o["[object Uint32Array]"]=!0,o["[object Arguments]"]=o["[object Array]"]=o["[object ArrayBuffer]"]=o["[object Boolean]"]=o["[object DataView]"]=o["[object Date]"]=o["[object Error]"]=o["[object Function]"]=o["[object Map]"]=o["[object Number]"]=o["[object Object]"]=o["[object RegExp]"]=o["[object Set]"]=o["[object String]"]=o["[object WeakMap]"]=!1,t.exports=function(t){return a(t)&&r(t.length)&&!!o[i(t)]}},313:(t,e,n)=>{var i=n(3218),r=n(5726),a=n(3498),o=Object.prototype.hasOwnProperty;t.exports=function(t){if(!i(t))return a(t);var e=r(t),n=[];for(var s in t)("constructor"!=s||!e&&o.call(t,s))&&n.push(s);return n}},2980:(t,e,n)=>{var i=n(6384),r=n(6556),a=n(8483),o=n(9783),s=n(3218),l=n(1704),h=n(6390);t.exports=function u(t,e,n,c,p){t!==e&&a(e,(function(a,l){if(p||(p=new i),s(a))o(t,e,l,n,u,c,p);else{var d=c?c(h(t,l),a,l+"",t,e,p):undefined;d===undefined&&(d=a),r(t,l,d)}}),l)}},9783:(t,e,n)=>{var i=n(6556),r=n(4626),a=n(7133),o=n(278),s=n(8517),l=n(5694),h=n(1469),u=n(9246),c=n(4144),p=n(3560),d=n(3218),f=n(8630),g=n(6719),_=n(6390),m=n(9881);t.exports=function(t,e,n,y,v,L,b){var k=_(t,n),M=_(e,n),x=b.get(M);if(x)i(t,n,x);else{var w=L?L(k,M,n+"",t,e,b):undefined,C=w===undefined;if(C){var P=h(M),E=!P&&c(M),S=!P&&!E&&g(M);w=M,P||E||S?h(k)?w=k:u(k)?w=o(k):E?(C=!1,w=r(M,!0)):S?(C=!1,w=a(M,!0)):w=[]:f(M)||l(M)?(w=k,l(k)?w=m(k):d(k)&&!p(k)||(w=s(M))):C=!1}C&&(b.set(M,w),v(w,M,y,L,b),b["delete"](M)),i(t,n,w)}}},5976:(t,e,n)=>{var i=n(6557),r=n(5357),a=n(61);t.exports=function(t,e){return a(r(t,e,i),t+"")}},6560:(t,e,n)=>{var i=n(5703),r=n(8777),a=n(6557),o=r?function(t,e){return r(t,"toString",{configurable:!0,enumerable:!1,value:i(e),writable:!0})}:a;t.exports=o},2545:t=>{t.exports=function(t,e){for(var n=-1,i=Array(t);++n{var i=n(2705),r=n(9932),a=n(1469),o=n(3448),s=i?i.prototype:undefined,l=s?s.toString:undefined;t.exports=function h(t){if("string"==typeof t)return t;if(a(t))return r(t,h)+"";if(o(t))return l?l.call(t):"";var e=t+"";return"0"==e&&1/t==-Infinity?"-0":e}},1717:t=>{t.exports=function(t){return function(e){return t(e)}}},1811:(t,e,n)=>{var i=n(1469),r=n(5403),a=n(5514),o=n(9833);t.exports=function(t,e){return i(t)?t:r(t,e)?[t]:a(o(t))}},4318:(t,e,n)=>{var i=n(1149);t.exports=function(t){var e=new t.constructor(t.byteLength);return new i(e).set(new i(t)),e}},4626:(t,e,n)=>{t=n.nmd(t);var i=n(5639),r=e&&!e.nodeType&&e,a=r&&t&&!t.nodeType&&t,o=a&&a.exports===r?i.Buffer:undefined,s=o?o.allocUnsafe:undefined;t.exports=function(t,e){if(e)return t.slice();var n=t.length,i=s?s(n):new t.constructor(n);return t.copy(i),i}},7133:(t,e,n)=>{var i=n(4318);t.exports=function(t,e){var n=e?i(t.buffer):t.buffer;return new t.constructor(n,t.byteOffset,t.length)}},278:t=>{t.exports=function(t,e){var n=-1,i=t.length;for(e||(e=Array(i));++n{var i=n(4865),r=n(9465);t.exports=function(t,e,n,a){var o=!n;n||(n={});for(var s=-1,l=e.length;++s{var i=n(5639)["__core-js_shared__"];t.exports=i},1463:(t,e,n)=>{var i=n(5976),r=n(6612);t.exports=function(t){return i((function(e,n){var i=-1,a=n.length,o=a>1?n[a-1]:undefined,s=a>2?n[2]:undefined;for(o=t.length>3&&"function"==typeof o?(a--,o):undefined,s&&r(n[0],n[1],s)&&(o=a<3?undefined:o,a=1),e=Object(e);++i{t.exports=function(t){return function(e,n,i){for(var r=-1,a=Object(e),o=i(e),s=o.length;s--;){var l=o[t?s:++r];if(!1===n(a[l],l,a))break}return e}}},8777:(t,e,n)=>{var i=n(852),r=function(){try{var t=i(Object,"defineProperty");return t({},"",{}),t}catch(e){}}();t.exports=r},1957:(t,e,n)=>{var i="object"==typeof n.g&&n.g&&n.g.Object===Object&&n.g;t.exports=i},5050:(t,e,n)=>{var i=n(7019);t.exports=function(t,e){var n=t.__data__;return i(e)?n["string"==typeof e?"string":"hash"]:n.map}},852:(t,e,n)=>{var i=n(8458),r=n(7801);t.exports=function(t,e){var n=r(t,e);return i(n)?n:undefined}},5924:(t,e,n)=>{var i=n(5569)(Object.getPrototypeOf,Object);t.exports=i},9607:(t,e,n)=>{var i=n(2705),r=Object.prototype,a=r.hasOwnProperty,o=r.toString,s=i?i.toStringTag:undefined;t.exports=function(t){var e=a.call(t,s),n=t[s];try{t[s]=undefined;var i=!0}catch(l){}var r=o.call(t);return i&&(e?t[s]=n:delete t[s]),r}},7801:t=>{t.exports=function(t,e){return null==t?undefined:t[e]}},222:(t,e,n)=>{var i=n(1811),r=n(5694),a=n(1469),o=n(5776),s=n(1780),l=n(327);t.exports=function(t,e,n){for(var h=-1,u=(e=i(e,t)).length,c=!1;++h{var i=n(4536);t.exports=function(){this.__data__=i?i(null):{},this.size=0}},401:t=>{t.exports=function(t){var e=this.has(t)&&delete this.__data__[t];return this.size-=e?1:0,e}},7667:(t,e,n)=>{var i=n(4536),r=Object.prototype.hasOwnProperty;t.exports=function(t){var e=this.__data__;if(i){var n=e[t];return"__lodash_hash_undefined__"===n?undefined:n}return r.call(e,t)?e[t]:undefined}},1327:(t,e,n)=>{var i=n(4536),r=Object.prototype.hasOwnProperty;t.exports=function(t){var e=this.__data__;return i?e[t]!==undefined:r.call(e,t)}},1866:(t,e,n)=>{var i=n(4536);t.exports=function(t,e){var n=this.__data__;return this.size+=this.has(t)?0:1,n[t]=i&&e===undefined?"__lodash_hash_undefined__":e,this}},8517:(t,e,n)=>{var i=n(3118),r=n(5924),a=n(5726);t.exports=function(t){return"function"!=typeof t.constructor||a(t)?{}:i(r(t))}},5776:t=>{var e=/^(?:0|[1-9]\d*)$/;t.exports=function(t,n){var i=typeof t;return!!(n=null==n?9007199254740991:n)&&("number"==i||"symbol"!=i&&e.test(t))&&t>-1&&t%1==0&&t{var i=n(7813),r=n(8612),a=n(5776),o=n(3218);t.exports=function(t,e,n){if(!o(n))return!1;var s=typeof e;return!!("number"==s?r(n)&&a(e,n.length):"string"==s&&e in n)&&i(n[e],t)}},5403:(t,e,n)=>{var i=n(1469),r=n(3448),a=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,o=/^\w*$/;t.exports=function(t,e){if(i(t))return!1;var n=typeof t;return!("number"!=n&&"symbol"!=n&&"boolean"!=n&&null!=t&&!r(t))||(o.test(t)||!a.test(t)||null!=e&&t in Object(e))}},7019:t=>{t.exports=function(t){var e=typeof t;return"string"==e||"number"==e||"symbol"==e||"boolean"==e?"__proto__"!==t:null===t}},5346:(t,e,n)=>{var i,r=n(4429),a=(i=/[^.]+$/.exec(r&&r.keys&&r.keys.IE_PROTO||""))?"Symbol(src)_1."+i:"";t.exports=function(t){return!!a&&a in t}},5726:t=>{var e=Object.prototype;t.exports=function(t){var n=t&&t.constructor;return t===("function"==typeof n&&n.prototype||e)}},7040:t=>{t.exports=function(){this.__data__=[],this.size=0}},4125:(t,e,n)=>{var i=n(8470),r=Array.prototype.splice;t.exports=function(t){var e=this.__data__,n=i(e,t);return!(n<0)&&(n==e.length-1?e.pop():r.call(e,n,1),--this.size,!0)}},2117:(t,e,n)=>{var i=n(8470);t.exports=function(t){var e=this.__data__,n=i(e,t);return n<0?undefined:e[n][1]}},7518:(t,e,n)=>{var i=n(8470);t.exports=function(t){return i(this.__data__,t)>-1}},4705:(t,e,n)=>{var i=n(8470);t.exports=function(t,e){var n=this.__data__,r=i(n,t);return r<0?(++this.size,n.push([t,e])):n[r][1]=e,this}},4785:(t,e,n)=>{var i=n(1989),r=n(8407),a=n(7071);t.exports=function(){this.size=0,this.__data__={hash:new i,map:new(a||r),string:new i}}},1285:(t,e,n)=>{var i=n(5050);t.exports=function(t){var e=i(this,t)["delete"](t);return this.size-=e?1:0,e}},6e3:(t,e,n)=>{var i=n(5050);t.exports=function(t){return i(this,t).get(t)}},9916:(t,e,n)=>{var i=n(5050);t.exports=function(t){return i(this,t).has(t)}},5265:(t,e,n)=>{var i=n(5050);t.exports=function(t,e){var n=i(this,t),r=n.size;return n.set(t,e),this.size+=n.size==r?0:1,this}},4523:(t,e,n)=>{var i=n(8306);t.exports=function(t){var e=i(t,(function(t){return 500===n.size&&n.clear(),t})),n=e.cache;return e}},4536:(t,e,n)=>{var i=n(852)(Object,"create");t.exports=i},3498:t=>{t.exports=function(t){var e=[];if(null!=t)for(var n in Object(t))e.push(n);return e}},1167:(t,e,n)=>{t=n.nmd(t);var i=n(1957),r=e&&!e.nodeType&&e,a=r&&t&&!t.nodeType&&t,o=a&&a.exports===r&&i.process,s=function(){try{var t=a&&a.require&&a.require("util").types;return t||o&&o.binding&&o.binding("util")}catch(e){}}();t.exports=s},2333:t=>{var e=Object.prototype.toString;t.exports=function(t){return e.call(t)}},5569:t=>{t.exports=function(t,e){return function(n){return t(e(n))}}},5357:(t,e,n)=>{var i=n(6874),r=Math.max;t.exports=function(t,e,n){return e=r(e===undefined?t.length-1:e,0),function(){for(var a=arguments,o=-1,s=r(a.length-e,0),l=Array(s);++o{var i=n(1957),r="object"==typeof self&&self&&self.Object===Object&&self,a=i||r||Function("return this")();t.exports=a},6390:t=>{t.exports=function(t,e){if(("constructor"!==e||"function"!=typeof t[e])&&"__proto__"!=e)return t[e]}},61:(t,e,n)=>{var i=n(6560),r=n(1275)(i);t.exports=r},1275:t=>{var e=Date.now;t.exports=function(t){var n=0,i=0;return function(){var r=e(),a=16-(r-i);if(i=r,a>0){if(++n>=800)return arguments[0]}else n=0;return t.apply(undefined,arguments)}}},7465:(t,e,n)=>{var i=n(8407);t.exports=function(){this.__data__=new i,this.size=0}},3779:t=>{t.exports=function(t){var e=this.__data__,n=e["delete"](t);return this.size=e.size,n}},7599:t=>{t.exports=function(t){return this.__data__.get(t)}},4758:t=>{t.exports=function(t){return this.__data__.has(t)}},4309:(t,e,n)=>{var i=n(8407),r=n(7071),a=n(3369);t.exports=function(t,e){var n=this.__data__;if(n instanceof i){var o=n.__data__;if(!r||o.length<199)return o.push([t,e]),this.size=++n.size,this;n=this.__data__=new a(o)}return n.set(t,e),this.size=n.size,this}},5514:(t,e,n)=>{var i=n(4523),r=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,a=/\\(\\)?/g,o=i((function(t){var e=[];return 46===t.charCodeAt(0)&&e.push(""),t.replace(r,(function(t,n,i,r){e.push(i?r.replace(a,"$1"):n||t)})),e}));t.exports=o},327:(t,e,n)=>{var i=n(3448);t.exports=function(t){if("string"==typeof t||i(t))return t;var e=t+"";return"0"==e&&1/t==-Infinity?"-0":e}},346:t=>{var e=Function.prototype.toString;t.exports=function(t){if(null!=t){try{return e.call(t)}catch(n){}try{return t+""}catch(n){}}return""}},5703:t=>{t.exports=function(t){return function(){return t}}},7813:t=>{t.exports=function(t,e){return t===e||t!=t&&e!=e}},7361:(t,e,n)=>{var i=n(7786);t.exports=function(t,e,n){var r=null==t?undefined:i(t,e);return r===undefined?n:r}},8721:(t,e,n)=>{var i=n(8565),r=n(222);t.exports=function(t,e){return null!=t&&r(t,e,i)}},6557:t=>{t.exports=function(t){return t}},5694:(t,e,n)=>{var i=n(9454),r=n(7005),a=Object.prototype,o=a.hasOwnProperty,s=a.propertyIsEnumerable,l=i(function(){return arguments}())?i:function(t){return r(t)&&o.call(t,"callee")&&!s.call(t,"callee")};t.exports=l},1469:t=>{var e=Array.isArray;t.exports=e},8612:(t,e,n)=>{var i=n(3560),r=n(1780);t.exports=function(t){return null!=t&&r(t.length)&&!i(t)}},9246:(t,e,n)=>{var i=n(8612),r=n(7005);t.exports=function(t){return r(t)&&i(t)}},4144:(t,e,n)=>{t=n.nmd(t);var i=n(5639),r=n(5062),a=e&&!e.nodeType&&e,o=a&&t&&!t.nodeType&&t,s=o&&o.exports===a?i.Buffer:undefined,l=(s?s.isBuffer:undefined)||r;t.exports=l},3560:(t,e,n)=>{var i=n(4239),r=n(3218);t.exports=function(t){if(!r(t))return!1;var e=i(t);return"[object Function]"==e||"[object GeneratorFunction]"==e||"[object AsyncFunction]"==e||"[object Proxy]"==e}},1780:t=>{t.exports=function(t){return"number"==typeof t&&t>-1&&t%1==0&&t<=9007199254740991}},3218:t=>{t.exports=function(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}},7005:t=>{t.exports=function(t){return null!=t&&"object"==typeof t}},8630:(t,e,n)=>{var i=n(4239),r=n(5924),a=n(7005),o=Function.prototype,s=Object.prototype,l=o.toString,h=s.hasOwnProperty,u=l.call(Object);t.exports=function(t){if(!a(t)||"[object Object]"!=i(t))return!1;var e=r(t);if(null===e)return!0;var n=h.call(e,"constructor")&&e.constructor;return"function"==typeof n&&n instanceof n&&l.call(n)==u}},3448:(t,e,n)=>{var i=n(4239),r=n(7005);t.exports=function(t){return"symbol"==typeof t||r(t)&&"[object Symbol]"==i(t)}},6719:(t,e,n)=>{var i=n(8749),r=n(1717),a=n(1167),o=a&&a.isTypedArray,s=o?r(o):i;t.exports=s},1704:(t,e,n)=>{var i=n(4636),r=n(313),a=n(8612);t.exports=function(t){return a(t)?i(t,!0):r(t)}},8306:(t,e,n)=>{var i=n(3369);function r(t,e){if("function"!=typeof t||null!=e&&"function"!=typeof e)throw new TypeError("Expected a function");var n=function(){var i=arguments,r=e?e.apply(this,i):i[0],a=n.cache;if(a.has(r))return a.get(r);var o=t.apply(this,i);return n.cache=a.set(r,o)||a,o};return n.cache=new(r.Cache||i),n}r.Cache=i,t.exports=r},2492:(t,e,n)=>{var i=n(2980),r=n(1463)((function(t,e,n){i(t,e,n)}));t.exports=r},5062:t=>{t.exports=function(){return!1}},9881:(t,e,n)=>{var i=n(8363),r=n(1704);t.exports=function(t){return i(t,r(t))}},9833:(t,e,n)=>{var i=n(531);t.exports=function(t){return null==t?"":i(t)}},2676:function(t){t.exports=function(){"use strict";function t(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function e(t,e){for(var n=0;ne?1:t0))break;if(null===e.right)break;if(n(t,e.right.key)>0&&(l=e.right,e.right=l.left,l.left=e,null===(e=l).right))break;a.right=e,a=e,e=e.right}}return a.right=e.left,o.left=e.right,e.left=r.right,e.right=r.left,e}function o(t,e,n,r){var o=new i(t,e);if(null===n)return o.left=o.right=null,o;var s=r(t,(n=a(t,n,r)).key);return s<0?(o.left=n.left,o.right=n,n.left=null):s>=0&&(o.right=n.right,o.left=n,n.right=null),o}function s(t,e,n){var i=null,r=null;if(e){var o=n((e=a(t,e,n)).key,t);0===o?(i=e.left,r=e.right):o<0?(r=e.right,e.right=null,i=e):(i=e.left,e.left=null,r=e)}return{left:i,right:r}}function l(t,e,n){return null===e?t:(null===t||((e=a(t.key,e,n)).left=t),e)}function h(t,e,n,i,r){if(t){i(e+(n?"└── ":"├── ")+r(t)+"\n");var a=e+(n?" ":"│ ");t.left&&h(t.left,a,!1,i,r),t.right&&h(t.right,a,!0,i,r)}}var u=function(){function t(t){void 0===t&&(t=r),this._root=null,this._size=0,this._comparator=t}return t.prototype.insert=function(t,e){return this._size++,this._root=o(t,e,this._root,this._comparator)},t.prototype.add=function(t,e){var n=new i(t,e);null===this._root&&(n.left=n.right=null,this._size++,this._root=n);var r=this._comparator,o=a(t,this._root,r),s=r(t,o.key);return 0===s?this._root=o:(s<0?(n.left=o.left,n.right=o,o.left=null):s>0&&(n.right=o.right,n.left=o,o.right=null),this._size++,this._root=n),this._root},t.prototype.remove=function(t){this._root=this._remove(t,this._root,this._comparator)},t.prototype._remove=function(t,e,n){var i;return null===e?null:0===n(t,(e=a(t,e,n)).key)?(null===e.left?i=e.right:(i=a(t,e.left,n)).right=e.right,this._size--,i):e},t.prototype.pop=function(){var t=this._root;if(t){for(;t.left;)t=t.left;return this._root=a(t.key,this._root,this._comparator),this._root=this._remove(t.key,this._root,this._comparator),{key:t.key,data:t.data}}return null},t.prototype.findStatic=function(t){for(var e=this._root,n=this._comparator;e;){var i=n(t,e.key);if(0===i)return e;e=i<0?e.left:e.right}return null},t.prototype.find=function(t){return this._root&&(this._root=a(t,this._root,this._comparator),0!==this._comparator(t,this._root.key))?null:this._root},t.prototype.contains=function(t){for(var e=this._root,n=this._comparator;e;){var i=n(t,e.key);if(0===i)return!0;e=i<0?e.left:e.right}return!1},t.prototype.forEach=function(t,e){for(var n=this._root,i=[],r=!1;!r;)null!==n?(i.push(n),n=n.left):0!==i.length?(n=i.pop(),t.call(e,n),n=n.right):r=!0;return this},t.prototype.range=function(t,e,n,i){for(var r=[],a=this._comparator,o=this._root;0!==r.length||o;)if(o)r.push(o),o=o.left;else{if(a((o=r.pop()).key,e)>0)break;if(a(o.key,t)>=0&&n.call(i,o))return this;o=o.right}return this},t.prototype.keys=function(){var t=[];return this.forEach((function(e){var n=e.key;return t.push(n)})),t},t.prototype.values=function(){var t=[];return this.forEach((function(e){var n=e.data;return t.push(n)})),t},t.prototype.min=function(){return this._root?this.minNode(this._root).key:null},t.prototype.max=function(){return this._root?this.maxNode(this._root).key:null},t.prototype.minNode=function(t){if(void 0===t&&(t=this._root),t)for(;t.left;)t=t.left;return t},t.prototype.maxNode=function(t){if(void 0===t&&(t=this._root),t)for(;t.right;)t=t.right;return t},t.prototype.at=function(t){for(var e=this._root,n=!1,i=0,r=[];!n;)if(e)r.push(e),e=e.left;else if(r.length>0){if(e=r.pop(),i===t)return e;i++,e=e.right}else n=!0;return null},t.prototype.next=function(t){var e=this._root,n=null;if(t.right){for(n=t.right;n.left;)n=n.left;return n}for(var i=this._comparator;e;){var r=i(t.key,e.key);if(0===r)break;r<0?(n=e,e=e.left):e=e.right}return n},t.prototype.prev=function(t){var e=this._root,n=null;if(null!==t.left){for(n=t.left;n.right;)n=n.right;return n}for(var i=this._comparator;e;){var r=i(t.key,e.key);if(0===r)break;r<0?e=e.left:(n=e,e=e.right)}return n},t.prototype.clear=function(){return this._root=null,this._size=0,this},t.prototype.toList=function(){return d(this._root)},t.prototype.load=function(t,e,n){void 0===e&&(e=[]),void 0===n&&(n=!1);var i=t.length,r=this._comparator;if(n&&_(t,e,0,i-1,r),null===this._root)this._root=c(t,e,0,i),this._size=i;else{var a=g(this.toList(),p(t,e),r);i=this._size+i,this._root=f({head:a},0,i)}return this},t.prototype.isEmpty=function(){return null===this._root},Object.defineProperty(t.prototype,"size",{get:function(){return this._size},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"root",{get:function(){return this._root},enumerable:!0,configurable:!0}),t.prototype.toString=function(t){void 0===t&&(t=function(t){return String(t.key)});var e=[];return h(this._root,"",!0,(function(t){return e.push(t)}),t),e.join("")},t.prototype.update=function(t,e,n){var i=this._comparator,r=s(t,this._root,i),a=r.left,h=r.right;i(t,e)<0?h=o(e,n,h,i):a=o(e,n,a,i),this._root=l(a,h,i)},t.prototype.split=function(t){return s(t,this._root,this._comparator)},t}();function c(t,e,n,r){var a=r-n;if(a>0){var o=n+Math.floor(a/2),s=t[o],l=e[o],h=new i(s,l);return h.left=c(t,e,n,o),h.right=c(t,e,o+1,r),h}return null}function p(t,e){for(var n=new i(null,null),r=n,a=0;a0?e=(e=o=o.next=n.pop()).right:r=!0;return o.next=null,a.next}function f(t,e,n){var i=n-e;if(i>0){var r=e+Math.floor(i/2),a=f(t,e,r),o=t.head;return o.left=a,t.head=t.head.next,o.right=f(t,r+1,n),o}return null}function g(t,e,n){for(var r=new i(null,null),a=r,o=t,s=e;null!==o&&null!==s;)n(o.key,s.key)<0?(a.next=o,o=o.next):(a.next=s,s=s.next),a=a.next;return null!==o?a.next=o:null!==s&&(a.next=s),r.next}function _(t,e,n,i,r){if(!(n>=i)){for(var a=t[n+i>>1],o=n-1,s=i+1;;){do{o++}while(r(t[o],a)<0);do{s--}while(r(t[s],a)>0);if(o>=s)break;var l=t[o];t[o]=t[s],t[s]=l,l=e[o],e[o]=e[s],e[s]=l}_(t,e,n,s,r),_(t,e,s+1,i,r)}}var m=function(t,e){return t.ll.x<=e.x&&e.x<=t.ur.x&&t.ll.y<=e.y&&e.y<=t.ur.y},y=function(t,e){if(e.ur.xe.x?1:t.ye.y?1:0}}]),n(e,[{key:"link",value:function(t){if(t.point===this.point)throw new Error("Tried to link already linked events");for(var e=t.point.events,n=0,i=e.length;n=0&&l>=0?oh?-1:0:a<0&&l<0?oh?1:0:la?1:0}}}]),e}(),I=0,j=function(){function e(n,i,r,a){t(this,e),this.id=++I,this.leftSE=n,n.segment=this,n.otherSE=i,this.rightSE=i,i.segment=this,i.otherSE=n,this.rings=r,this.windings=a}return n(e,null,[{key:"compare",value:function(t,e){var n=t.leftSE.point.x,i=e.leftSE.point.x,r=t.rightSE.point.x,a=e.rightSE.point.x;if(ao&&s>l)return-1;var u=t.comparePoint(e.leftSE.point);if(u<0)return 1;if(u>0)return-1;var c=e.comparePoint(t.rightSE.point);return 0!==c?c:-1}if(n>i){if(os&&o>h)return 1;var p=e.comparePoint(t.leftSE.point);if(0!==p)return p;var d=t.comparePoint(e.rightSE.point);return d<0?1:d>0?-1:1}if(os)return 1;if(ra){var g=t.comparePoint(e.rightSE.point);if(g<0)return 1;if(g>0)return-1}if(r!==a){var _=l-o,m=r-n,y=h-s,v=a-i;if(_>m&&yv)return-1}return r>a?1:rh?1:t.ide.id?1:0}}]),n(e,[{key:"replaceRightSE",value:function(t){this.rightSE=t,this.rightSE.segment=this,this.rightSE.otherSE=this.leftSE,this.leftSE.otherSE=this.rightSE}},{key:"bbox",value:function(){var t=this.leftSE.point.y,e=this.rightSE.point.y;return{ll:{x:this.leftSE.point.x,y:te?t:e}}}},{key:"vector",value:function(){return{x:this.rightSE.point.x-this.leftSE.point.x,y:this.rightSE.point.y-this.leftSE.point.y}}},{key:"isAnEndpoint",value:function(t){return t.x===this.leftSE.point.x&&t.y===this.leftSE.point.y||t.x===this.rightSE.point.x&&t.y===this.rightSE.point.y}},{key:"comparePoint",value:function(t){if(this.isAnEndpoint(t))return 0;var e=this.leftSE.point,n=this.rightSE.point,i=this.vector();if(e.x===n.x)return t.x===e.x?0:t.x0&&s.swapEvents(),T.comparePoints(this.leftSE.point,this.rightSE.point)>0&&this.swapEvents(),i&&(r.checkForConsuming(),a.checkForConsuming()),n}},{key:"swapEvents",value:function(){var t=this.rightSE;this.rightSE=this.leftSE,this.leftSE=t,this.leftSE.isLeft=!0,this.rightSE.isLeft=!1;for(var e=0,n=this.windings.length;e0){var a=n;n=i,i=a}if(n.prev===i){var o=n;n=i,i=o}for(var s=0,l=i.rings.length;s0))throw new Error("Tried to create degenerate segment at [".concat(t.x,", ").concat(t.y,"]"));r=n,a=t,o=-1}return new e(new T(r,!0),new T(a,!1),[i],[o])}}]),e}(),G=function(){function e(n,i,r){if(t(this,e),!Array.isArray(n)||0===n.length)throw new Error("Input geometry is not a valid Polygon or MultiPolygon");if(this.poly=i,this.isExterior=r,this.segments=[],"number"!=typeof n[0][0]||"number"!=typeof n[0][1])throw new Error("Input geometry is not a valid Polygon or MultiPolygon");var a=x.round(n[0][0],n[0][1]);this.bbox={ll:{x:a.x,y:a.y},ur:{x:a.x,y:a.y}};for(var o=a,s=1,l=n.length;sthis.bbox.ur.x&&(this.bbox.ur.x=h.x),h.y>this.bbox.ur.y&&(this.bbox.ur.y=h.y),o=h)}a.x===o.x&&a.y===o.y||this.segments.push(j.fromRing(o,a,this))}return n(e,[{key:"getSweepEvents",value:function(){for(var t=[],e=0,n=this.segments.length;ethis.bbox.ur.x&&(this.bbox.ur.x=o.bbox.ur.x),o.bbox.ur.y>this.bbox.ur.y&&(this.bbox.ur.y=o.bbox.ur.y),this.interiorRings.push(o)}this.multiPoly=i}return n(e,[{key:"getSweepEvents",value:function(){for(var t=this.exteriorRing.getSweepEvents(),e=0,n=this.interiorRings.length;ethis.bbox.ur.x&&(this.bbox.ur.x=o.bbox.ur.x),o.bbox.ur.y>this.bbox.ur.y&&(this.bbox.ur.y=o.bbox.ur.y),this.polys.push(o)}this.isSubject=i}return n(e,[{key:"getSweepEvents",value:function(){for(var t=[],e=0,n=this.polys.length;e0&&(t=i)}for(var r=t.segment.prevInResult(),a=r?r.prevInResult():null;;){if(!r)return null;if(!a)return r.ringOut;if(a.ringOut!==r.ringOut)return a.ringOut.enclosingRing()!==r.ringOut?r.ringOut:r.ringOut.enclosingRing();r=a.prevInResult(),a=r?r.prevInResult():null}}}]),e}(),U=function(){function e(n){t(this,e),this.exteriorRing=n,n.poly=this,this.interiorRings=[]}return n(e,[{key:"addInterior",value:function(t){this.interiorRings.push(t),t.poly=this}},{key:"getGeom",value:function(){var t=[this.exteriorRing.getGeom()];if(null===t[0])return null;for(var e=0,n=this.interiorRings.length;e1&&arguments[1]!==undefined?arguments[1]:j.compare;t(this,e),this.queue=n,this.tree=new u(i),this.segments=[]}return n(e,[{key:"process",value:function(t){var e=t.segment,n=[];if(t.consumedBy)return t.isLeft?this.queue.remove(t.otherSE):this.tree.remove(e),n;var i=t.isLeft?this.tree.insert(e):this.tree.find(e);if(!i)throw new Error("Unable to find segment #".concat(e.id," ")+"[".concat(e.leftSE.point.x,", ").concat(e.leftSE.point.y,"] -> ")+"[".concat(e.rightSE.point.x,", ").concat(e.rightSE.point.y,"] ")+"in SweepLine tree. Please submit a bug report.");for(var r=i,a=i,o=undefined,s=undefined;o===undefined;)null===(r=this.tree.prev(r))?o=null:r.key.consumedBy===undefined&&(o=r.key);for(;s===undefined;)null===(a=this.tree.next(a))?s=null:a.key.consumedBy===undefined&&(s=a.key);if(t.isLeft){var l=null;if(o){var h=o.getIntersection(e);if(null!==h&&(e.isAnEndpoint(h)||(l=h),!o.isAnEndpoint(h)))for(var u=this._splitSafely(o,h),c=0,p=u.length;c0?(this.tree.remove(e),n.push(t)):(this.segments.push(e),e.prev=o)}else{if(o&&s){var k=o.getIntersection(s);if(null!==k){if(!o.isAnEndpoint(k))for(var M=this._splitSafely(o,k),x=0,w=M.length;xK)throw new Error("Infinite loop when putting segment endpoints in a priority queue (queue size too big). Please file a bug report.");for(var b=new F(f),k=f.size,M=f.pop();M;){var w=M.key;if(f.size===k){var C=w.segment;throw new Error("Unable to pop() ".concat(w.isLeft?"left":"right"," SweepEvent ")+"[".concat(w.point.x,", ").concat(w.point.y,"] from segment #").concat(C.id," ")+"[".concat(C.leftSE.point.x,", ").concat(C.leftSE.point.y,"] -> ")+"[".concat(C.rightSE.point.x,", ").concat(C.rightSE.point.y,"] from queue. ")+"Please file a bug report.")}if(f.size>K)throw new Error("Infinite loop when passing sweep line over endpoints (queue size too big). Please file a bug report.");if(b.segments.length>H)throw new Error("Infinite loop when passing sweep line over endpoints (too many sweep line segments). Please file a bug report.");for(var P=b.process(w),E=0,S=P.length;E1?e-1:0),i=1;i1?e-1:0),i=1;i1?e-1:0),i=1;i1?e-1:0),i=1;ii;){if(r-i>600){var o=r-i+1,l=n-i+1,h=Math.log(o),u=.5*Math.exp(2*h/3),c=.5*Math.sqrt(h*u*(o-u)/o)*(l-o/2<0?-1:1);s(t,n,Math.max(i,Math.floor(n-l*u/o+c)),Math.min(r,Math.floor(n+(o-l)*u/o+c)),a)}var p=t[n],d=i,f=r;for(e(t,i,n),a(t[r],p)>0&&e(t,i,r);d0;)f--}0===a(t[i],p)?e(t,i,f):e(t,++f,r),f<=n&&(i=f+1),n<=f&&(r=f-1)}}(t,i,r||0,a||t.length-1,o||n)}function e(t,e,n){var i=t[e];t[e]=t[n],t[n]=i}function n(t,e){return te?1:0}var i=function(t){void 0===t&&(t=9),this._maxEntries=Math.max(4,t),this._minEntries=Math.max(2,Math.ceil(.4*this._maxEntries)),this.clear()};function r(t,e,n){if(!n)return e.indexOf(t);for(var i=0;i=t.minX&&e.maxY>=t.minY}function f(t){return{children:t,height:1,leaf:!0,minX:1/0,minY:1/0,maxX:-1/0,maxY:-1/0}}function g(e,n,i,r,a){for(var o=[n,i];o.length;)if(!((i=o.pop())-(n=o.pop())<=r)){var s=n+Math.ceil((i-n)/r/2)*r;t(e,s,n,i,a),o.push(n,s,s,i)}}return i.prototype.all=function(){return this._all(this.data,[])},i.prototype.search=function(t){var e=this.data,n=[];if(!d(t,e))return n;for(var i=this.toBBox,r=[];e;){for(var a=0;a=0&&r[e].children.length>this._maxEntries;)this._split(r,e),e--;this._adjustParentBBoxes(i,r,e)},i.prototype._split=function(t,e){var n=t[e],i=n.children.length,r=this._minEntries;this._chooseSplitAxis(n,r,i);var o=this._chooseSplitIndex(n,r,i),s=f(n.children.splice(o,n.children.length-o));s.height=n.height,s.leaf=n.leaf,a(n,this.toBBox),a(s,this.toBBox),e?t[e-1].children.push(s):this._splitRoot(n,s)},i.prototype._splitRoot=function(t,e){this.data=f([t,e]),this.data.height=t.height+1,this.data.leaf=!1,a(this.data,this.toBBox)},i.prototype._chooseSplitIndex=function(t,e,n){for(var i,r,a,s,l,h,c,p=1/0,d=1/0,f=e;f<=n-e;f++){var g=o(t,0,f,this.toBBox),_=o(t,f,n,this.toBBox),m=(r=g,a=_,s=void 0,l=void 0,h=void 0,c=void 0,s=Math.max(r.minX,a.minX),l=Math.max(r.minY,a.minY),h=Math.min(r.maxX,a.maxX),c=Math.min(r.maxY,a.maxY),Math.max(0,h-s)*Math.max(0,c-l)),y=u(g)+u(_);m=e;d--){var f=t.children[d];s(l,t.leaf?r(f):f),h+=c(l)}return h},i.prototype._adjustParentBBoxes=function(t,e,n){for(var i=n;i>=0;i--)s(e[i],t)},i.prototype._condense=function(t){for(var e=t.length-1,n=void 0;e>=0;e--)0===t[e].children.length?e>0?(n=t[e-1].children).splice(n.indexOf(t[e]),1):this.clear():a(t[e],this.toBBox)},i}()}},e={};function n(i){var r=e[i];if(r!==undefined)return r.exports;var a=e[i]={id:i,loaded:!1,exports:{}};return t[i].call(a.exports,a,a.exports,n),a.loaded=!0,a.exports}n.n=t=>{var e=t&&t.__esModule?()=>t["default"]:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var i in e)n.o(e,i)&&!n.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),n.nmd=t=>(t.paths=[],t.children||(t.children=[]),t);n(1052)})(); diff --git a/ui-ngx/patches/leaflet+1.7.1.patch b/ui-ngx/patches/leaflet+1.7.1.patch deleted file mode 100644 index b7688d4f33..0000000000 --- a/ui-ngx/patches/leaflet+1.7.1.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/leaflet/dist/leaflet-src.js b/node_modules/leaflet/dist/leaflet-src.js -index 9acc7da..6c9acf8 100644 ---- a/node_modules/leaflet/dist/leaflet-src.js -+++ b/node_modules/leaflet/dist/leaflet-src.js -@@ -2050,6 +2050,8 @@ - obj.removeEventListener(POINTER_CANCEL, handler, false); - } - -+ delete obj['_leaflet_' + type + id]; -+ - return this; - } - diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml index 5bf5867398..dade6f8c28 100644 --- a/ui-ngx/pom.xml +++ b/ui-ngx/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.1-SNAPSHOT + 3.4.2-SNAPSHOT thingsboard org.thingsboard diff --git a/ui-ngx/src/app/core/api/data-aggregator.ts b/ui-ngx/src/app/core/api/data-aggregator.ts index 7485a4bc87..6e77a0a161 100644 --- a/ui-ngx/src/app/core/api/data-aggregator.ts +++ b/ui-ngx/src/app/core/api/data-aggregator.ts @@ -14,7 +14,10 @@ /// limitations under the License. /// -import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models'; +import { + AggKey, + IndexedSubscriptionData, +} from '@app/shared/models/telemetry/telemetry.models'; import { AggregationType, calculateIntervalComparisonEndTime, @@ -25,10 +28,10 @@ import { SubscriptionTimewindow } from '@shared/models/time/time.models'; import { UtilsService } from '@core/services/utils.service'; -import { deepClone, isNumber, isNumeric } from '@core/utils'; +import { deepClone, isDefinedAndNotNull, isNumber, isNumeric } from '@core/utils'; import Timeout = NodeJS.Timeout; -export declare type onAggregatedData = (data: SubscriptionData, detectChanges: boolean) => void; +export declare type onAggregatedData = (data: IndexedSubscriptionData, detectChanges: boolean) => void; interface AggData { count: number; @@ -67,12 +70,12 @@ class AggDataMap { } class AggregationMap { - aggMap: {[key: string]: AggDataMap} = {}; + aggMap: {[id: number]: AggDataMap} = {}; detectRangeChanged(): boolean { let changed = false; - for (const key of Object.keys(this.aggMap)) { - const aggDataMap = this.aggMap[key]; + for (const id of Object.keys(this.aggMap)) { + const aggDataMap = this.aggMap[id]; if (aggDataMap.rangeChanged) { changed = true; aggDataMap.rangeChanged = false; @@ -82,8 +85,8 @@ class AggregationMap { } clearRangeChangedFlags() { - for (const key of Object.keys(this.aggMap)) { - this.aggMap[key].rangeChanged = false; + for (const id of Object.keys(this.aggMap)) { + this.aggMap[id].rangeChanged = false; } } } @@ -93,7 +96,7 @@ declare type AggFunction = (aggData: AggData, value?: any) => void; const avg: AggFunction = (aggData: AggData, value?: any) => { aggData.count++; if (isNumber(value)) { - aggData.sum += value; + aggData.sum = aggData.aggValue * (aggData.count - 1) + value; aggData.aggValue = aggData.sum / aggData.count; } else { aggData.aggValue = value; @@ -135,9 +138,25 @@ const none: AggFunction = (aggData: AggData, value?: any) => { export class DataAggregator { - private dataBuffer: SubscriptionData = {}; - private data: SubscriptionData; - private readonly lastPrevKvPairData: {[key: string]: [number, any]}; + constructor(private onDataCb: onAggregatedData, + private tsKeys: AggKey[], + private isLatestDataAgg: boolean, + private subsTw: SubscriptionTimewindow, + private utils: UtilsService, + private ignoreDataUpdateOnIntervalTick: boolean) { + this.tsKeys.forEach((key) => { + if (!this.dataBuffer[key.id]) { + this.dataBuffer[key.id] = []; + } + }); + if (this.subsTw.aggregation.stateData) { + this.lastPrevKvPairData = {}; + } + } + + private dataBuffer: IndexedSubscriptionData = []; + private data: IndexedSubscriptionData; + private readonly lastPrevKvPairData: {[id: number]: [number, any]}; private aggregationMap: AggregationMap; @@ -145,9 +164,7 @@ export class DataAggregator { private resetPending = false; private updatedData = false; - private noAggregation = this.subsTw.aggregation.type === AggregationType.NONE; - private aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000); - private readonly aggFunction: AggFunction; + private aggregationTimeout = this.isLatestDataAgg ? 1000 : Math.max(this.subsTw.aggregation.interval, 1000); private intervalTimeoutHandle: Timeout; private intervalScheduledTime: number; @@ -156,38 +173,29 @@ export class DataAggregator { private endTs: number; private elapsed: number; - constructor(private onDataCb: onAggregatedData, - private tsKeyNames: string[], - private subsTw: SubscriptionTimewindow, - private utils: UtilsService, - private ignoreDataUpdateOnIntervalTick: boolean) { - this.tsKeyNames.forEach((key) => { - this.dataBuffer[key] = []; - }); - if (this.subsTw.aggregation.stateData) { - this.lastPrevKvPairData = {}; + private static convertValue(val: string, noAggregation: boolean): any { + if (val && isNumeric(val) && (!noAggregation || noAggregation && Number(val).toString() === val)) { + return Number(val); } - switch (this.subsTw.aggregation.type) { + return val; + } + + private static getAggFunction(aggType: AggregationType): AggFunction { + switch (aggType) { case AggregationType.MIN: - this.aggFunction = min; - break; + return min; case AggregationType.MAX: - this.aggFunction = max; - break; + return max; case AggregationType.AVG: - this.aggFunction = avg; - break; + return avg; case AggregationType.SUM: - this.aggFunction = sum; - break; + return sum; case AggregationType.COUNT: - this.aggFunction = count; - break; + return count; case AggregationType.NONE: - this.aggFunction = none; - break; + return none; default: - this.aggFunction = avg; + return avg; } } @@ -206,7 +214,7 @@ export class DataAggregator { this.intervalScheduledTime = this.utils.currentPerfTime(); this.calculateStartEndTs(); this.elapsed = 0; - this.aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000); + this.aggregationTimeout = this.isLatestDataAgg ? 1000 : Math.max(this.subsTw.aggregation.interval, 1000); this.resetPending = true; this.updatedData = false; this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout); @@ -220,7 +228,7 @@ export class DataAggregator { this.aggregationMap = null; } - public onData(data: SubscriptionDataHolder, update: boolean, history: boolean, detectChanges: boolean) { + public onData(data: IndexedSubscriptionData, update: boolean, history: boolean, detectChanges: boolean) { this.updatedData = true; if (!this.dataReceived || this.resetPending) { let updateIntervalScheduledTime = true; @@ -235,9 +243,9 @@ export class DataAggregator { } if (update) { this.aggregationMap = new AggregationMap(); - this.updateAggregatedData(data.data); + this.updateAggregatedData(data); } else { - this.aggregationMap = this.processAggregatedData(data.data); + this.aggregationMap = this.processAggregatedData(data); } if (updateIntervalScheduledTime) { this.intervalScheduledTime = this.utils.currentPerfTime(); @@ -245,7 +253,7 @@ export class DataAggregator { this.aggregationMap.clearRangeChangedFlags(); this.onInterval(history, detectChanges); } else { - this.updateAggregatedData(data.data); + this.updateAggregatedData(data); if (history) { this.intervalScheduledTime = this.utils.currentPerfTime(); this.onInterval(history, detectChanges); @@ -283,9 +291,9 @@ export class DataAggregator { } const intervalTimeout = rangeChanged ? this.aggregationTimeout - this.elapsed : this.aggregationTimeout; if (!history) { - const delta = Math.floor(this.elapsed / this.subsTw.aggregation.interval); + const delta = Math.floor(this.elapsed / this.aggregationTimeout); if (delta || !this.data || rangeChanged) { - const tickTs = delta * this.subsTw.aggregation.interval; + const tickTs = delta * this.aggregationTimeout; if (this.subsTw.quickInterval) { const startEndTime = calculateIntervalStartEndTime(this.subsTw.quickInterval, this.subsTw.timezone); this.startTs = startEndTime[0] + this.subsTw.tsOffset; @@ -295,7 +303,7 @@ export class DataAggregator { this.endTs += tickTs; } this.data = this.updateData(); - this.elapsed = this.elapsed - delta * this.subsTw.aggregation.interval; + this.elapsed = this.elapsed - delta * this.aggregationTimeout; } } else { this.data = this.updateData(); @@ -309,39 +317,45 @@ export class DataAggregator { } } - private updateData(): SubscriptionData { - this.tsKeyNames.forEach((key) => { - this.dataBuffer[key] = []; + private updateData(): IndexedSubscriptionData { + this.dataBuffer = []; + this.tsKeys.forEach((key) => { + if (!this.dataBuffer[key.id]) { + this.dataBuffer[key.id] = []; + } }); - for (const key of Object.keys(this.aggregationMap.aggMap)) { - const aggKeyData = this.aggregationMap.aggMap[key]; - let keyData = this.dataBuffer[key]; + for (const idStr of Object.keys(this.aggregationMap.aggMap)) { + const id = Number(idStr); + const aggKeyData = this.aggregationMap.aggMap[id]; + const aggKey = this.aggKeyById(id); + const noAggregation = aggKey.agg === AggregationType.NONE; + let keyData = this.dataBuffer[id]; aggKeyData.forEach((aggData, aggTimestamp) => { if (aggTimestamp < this.startTs) { if (this.subsTw.aggregation.stateData && - (!this.lastPrevKvPairData[key] || this.lastPrevKvPairData[key][0] < aggTimestamp)) { - this.lastPrevKvPairData[key] = [aggTimestamp, aggData.aggValue]; + (!this.lastPrevKvPairData[id] || this.lastPrevKvPairData[id][0] < aggTimestamp)) { + this.lastPrevKvPairData[id] = [aggTimestamp, aggData.aggValue]; } aggKeyData.delete(aggTimestamp); this.updatedData = true; - } else if (aggTimestamp < this.endTs || this.noAggregation) { + } else if (aggTimestamp < this.endTs || noAggregation) { const kvPair: [number, any] = [aggTimestamp, aggData.aggValue]; keyData.push(kvPair); } }); keyData.sort((set1, set2) => set1[0] - set2[0]); if (this.subsTw.aggregation.stateData) { - this.updateStateBounds(keyData, deepClone(this.lastPrevKvPairData[key])); + this.updateStateBounds(keyData, deepClone(this.lastPrevKvPairData[id])); } if (keyData.length > this.subsTw.aggregation.limit) { keyData = keyData.slice(keyData.length - this.subsTw.aggregation.limit); } - this.dataBuffer[key] = keyData; + this.dataBuffer[id] = keyData; } return this.dataBuffer; } - private updateStateBounds(keyData: [number, any][], lastPrevKvPair: [number, any]) { + private updateStateBounds(keyData: [number, any, number?][], lastPrevKvPair: [number, any]) { if (lastPrevKvPair) { lastPrevKvPair[0] = this.startTs; } @@ -369,66 +383,71 @@ export class DataAggregator { } } - private processAggregatedData(data: SubscriptionData): AggregationMap { - const isCount = this.subsTw.aggregation.type === AggregationType.COUNT; + private processAggregatedData(data: IndexedSubscriptionData): AggregationMap { const aggregationMap = new AggregationMap(); - for (const key of Object.keys(data)) { - let aggKeyData = aggregationMap.aggMap[key]; + for (const idStr of Object.keys(data)) { + const id = Number(idStr); + const aggKey = this.aggKeyById(id); + const aggType = aggKey.agg; + const isCount = aggType === AggregationType.COUNT; + const noAggregation = aggType === AggregationType.NONE; + let aggKeyData = aggregationMap.aggMap[id]; if (!aggKeyData) { aggKeyData = new AggDataMap(); - aggregationMap.aggMap[key] = aggKeyData; + aggregationMap.aggMap[id] = aggKeyData; } - const keyData = data[key]; + const keyData = data[id]; keyData.forEach((kvPair) => { const timestamp = kvPair[0]; - const value = this.convertValue(kvPair[1]); - const aggKey = timestamp; + const value = DataAggregator.convertValue(kvPair[1], noAggregation); + const tsKey = timestamp; const aggData = { - count: isCount ? value : 1, + count: isCount ? value : isDefinedAndNotNull(kvPair[2]) ? kvPair[2] : 1, sum: value, aggValue: value }; - aggKeyData.set(aggKey, aggData); + aggKeyData.set(tsKey, aggData); }); } return aggregationMap; } - private updateAggregatedData(data: SubscriptionData) { - const isCount = this.subsTw.aggregation.type === AggregationType.COUNT; - for (const key of Object.keys(data)) { - let aggKeyData = this.aggregationMap.aggMap[key]; + private updateAggregatedData(data: IndexedSubscriptionData) { + for (const idStr of Object.keys(data)) { + const id = Number(idStr); + const aggKey = this.aggKeyById(id); + const aggType = aggKey.agg; + const isCount = aggType === AggregationType.COUNT; + const noAggregation = aggType === AggregationType.NONE; + let aggKeyData = this.aggregationMap.aggMap[id]; if (!aggKeyData) { aggKeyData = new AggDataMap(); - this.aggregationMap.aggMap[key] = aggKeyData; + this.aggregationMap.aggMap[id] = aggKeyData; } - const keyData = data[key]; + const keyData = data[id]; keyData.forEach((kvPair) => { const timestamp = kvPair[0]; - const value = this.convertValue(kvPair[1]); - const aggTimestamp = this.noAggregation ? timestamp : (this.startTs + + const value = DataAggregator.convertValue(kvPair[1], noAggregation); + const aggTimestamp = noAggregation ? timestamp : (this.startTs + Math.floor((timestamp - this.startTs) / this.subsTw.aggregation.interval) * this.subsTw.aggregation.interval + this.subsTw.aggregation.interval / 2); let aggData = aggKeyData.get(aggTimestamp); if (!aggData) { aggData = { - count: 1, + count: isDefinedAndNotNull(kvPair[2]) ? kvPair[2] : 1, sum: value, aggValue: isCount ? 1 : value }; aggKeyData.set(aggTimestamp, aggData); } else { - this.aggFunction(aggData, value); + DataAggregator.getAggFunction(aggType)(aggData, value); } }); } } - private convertValue(val: string): any { - if (val && isNumeric(val) && (!this.noAggregation || this.noAggregation && Number(val).toString() === val)) { - return Number(val); - } - return val; + private aggKeyById(id: number): AggKey { + return this.tsKeys.find(key => key.id === id); } } diff --git a/ui-ngx/src/app/core/api/entity-data-subscription.ts b/ui-ngx/src/app/core/api/entity-data-subscription.ts index 9cffe57ae7..4dc0e7c4e1 100644 --- a/ui-ngx/src/app/core/api/entity-data-subscription.ts +++ b/ui-ngx/src/app/core/api/entity-data-subscription.ts @@ -14,9 +14,16 @@ /// limitations under the License. /// -import { DataSet, DataSetHolder, DatasourceType, widgetType } from '@shared/models/widget.models'; -import { AggregationType, getCurrentTime, SubscriptionTimewindow } from '@shared/models/time/time.models'; +import { ComparisonResultType, DataSet, DataSetHolder, DatasourceType, widgetType } from '@shared/models/widget.models'; import { + AggregationType, + ComparisonDuration, + createTimewindowForComparison, + getCurrentTime, + SubscriptionTimewindow +} from '@shared/models/time/time.models'; +import { + ComparisonTsValue, EntityData, EntityDataPageLink, EntityFilter, @@ -28,11 +35,12 @@ import { TsValue } from '@shared/models/query/query.models'; import { + AggKey, DataKeyType, EntityCountCmd, EntityDataCmd, + IndexedSubscriptionData, SubscriptionData, - SubscriptionDataHolder, TelemetryService, TelemetrySubscriber } from '@shared/models/telemetry/telemetry.models'; @@ -55,6 +63,11 @@ declare type DataUpdatedCb = (data: DataSetHolder, dataIndex: number, export interface SubscriptionDataKey { name: string; type: DataKeyType; + aggregationType?: AggregationType; + comparisonEnabled?: boolean; + timeForComparison?: ComparisonDuration; + comparisonCustomIntervalValue?: number; + comparisonResultType?: ComparisonResultType; funcBody: string; func?: DataKeyFunction; postFuncBody: string; @@ -82,9 +95,16 @@ export interface EntityDataSubscriptionOptions { export class EntityDataSubscription { + constructor(private listener: EntityDataListener, + private telemetryService: TelemetryService, + private utils: UtilsService) { + this.initializeSubscription(); + } + private entityDataSubscriptionOptions = this.listener.subscriptionOptions; private datasourceType: DatasourceType = this.entityDataSubscriptionOptions.datasourceType; private history: boolean; + private isFloatingTimewindow: boolean; private realtime: boolean; private subscriber: TelemetrySubscriber; @@ -95,13 +115,18 @@ export class EntityDataSubscription { private attrFields: Array; private tsFields: Array; private latestValues: Array; + private aggTsValues: Array; + private aggTsComparisonValues: Array; private entityDataResolveSubject: Subject; private pageData: PageData; + private data: Array>; private subsTw: SubscriptionTimewindow; private latestTsOffset: number; private dataAggregators: Array; + private tsLatestDataAggregators: Array; private dataKeys: {[key: string]: Array | SubscriptionDataKey} = {}; + private dataKeysList: SubscriptionDataKey[] = []; private datasourceData: {[index: number]: {[key: string]: DataSetHolder}}; private datasourceOrigData: {[index: number]: {[key: string]: DataSetHolder}}; private entityIdToDataIndex: {[id: string]: number}; @@ -116,15 +141,44 @@ export class EntityDataSubscription { private dataResolved = false; private started = false; - constructor(private listener: EntityDataListener, - private telemetryService: TelemetryService, - private utils: UtilsService) { - this.initializeSubscription(); + private static convertValue(val: string): any { + if (val && isNumeric(val) && Number(val).toString() === val) { + return Number(val); + } + return val; + } + + private static calculateComparisonValue(key: SubscriptionDataKey, comparisonTsValue: ComparisonTsValue): [number, any, number?][] { + let timestamp: number; + let value: any; + switch (key.comparisonResultType) { + case ComparisonResultType.PREVIOUS_VALUE: + timestamp = comparisonTsValue.previous.ts; + value = comparisonTsValue.previous.value; + break; + case ComparisonResultType.DELTA_ABSOLUTE: + case ComparisonResultType.DELTA_PERCENT: + timestamp = comparisonTsValue.previous.ts; + const currentVal = EntityDataSubscription.convertValue(comparisonTsValue.current.value); + const prevVal = EntityDataSubscription.convertValue(comparisonTsValue.previous.value); + if (isNumeric(currentVal) && isNumeric(prevVal)) { + if (key.comparisonResultType === ComparisonResultType.DELTA_ABSOLUTE) { + value = currentVal - prevVal; + } else { + value = (currentVal - prevVal) / prevVal * 100; + } + } else { + value = ''; + } + break; + } + return [[timestamp, value]]; } private initializeSubscription() { for (let i = 0; i < this.entityDataSubscriptionOptions.dataKeys.length; i++) { const dataKey = deepClone(this.entityDataSubscriptionOptions.dataKeys[i]); + this.dataKeysList.push(dataKey); dataKey.index = i; if (this.datasourceType === DatasourceType.function) { if (!dataKey.func) { @@ -142,7 +196,8 @@ export class EntityDataSubscription { if (this.datasourceType === DatasourceType.function) { key = `${dataKey.name}_${dataKey.index}_${dataKey.type}${dataKey.latest ? '_latest' : ''}`; } else { - key = `${dataKey.name}_${dataKey.type}${dataKey.latest ? '_latest' : ''}`; + const keyIndexSuffix = dataKey.aggregationType && dataKey.aggregationType !== AggregationType.NONE ? `_${dataKey.index}` : ''; + key = `${dataKey.name}_${dataKey.type}${keyIndexSuffix}${dataKey.latest ? '_latest' : ''}`; } let dataKeysList = this.dataKeys[key] as Array; if (!dataKeysList) { @@ -180,6 +235,12 @@ export class EntityDataSubscription { }); this.dataAggregators = null; } + if (this.tsLatestDataAggregators) { + this.tsLatestDataAggregators.forEach((aggregator) => { + aggregator.destroy(); + }); + this.tsLatestDataAggregators = null; + } this.pageData = null; } @@ -188,16 +249,11 @@ export class EntityDataSubscription { if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) { this.started = true; this.dataResolved = true; - this.subsTw = this.entityDataSubscriptionOptions.subscriptionTimewindow; - this.latestTsOffset = this.entityDataSubscriptionOptions.latestTsOffset; - this.history = this.entityDataSubscriptionOptions.subscriptionTimewindow && - isObject(this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow); - this.realtime = this.entityDataSubscriptionOptions.subscriptionTimewindow && - isDefinedAndNotNull(this.entityDataSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs); + this.prepareSubscriptionTimewindow(); } if (this.datasourceType === DatasourceType.entity) { const entityFields: Array = - this.entityDataSubscriptionOptions.dataKeys.filter(dataKey => dataKey.type === DataKeyType.entityField).map( + this.dataKeysList.filter(dataKey => dataKey.type === DataKeyType.entityField).map( dataKey => ({ type: EntityKeyType.ENTITY_FIELD, key: dataKey.name }) ); if (!entityFields.find(key => key.key === 'name')) { @@ -219,18 +275,20 @@ export class EntityDataSubscription { }); } - this.attrFields = this.entityDataSubscriptionOptions.dataKeys.filter(dataKey => dataKey.type === DataKeyType.attribute).map( + this.attrFields = this.dataKeysList.filter(dataKey => dataKey.type === DataKeyType.attribute).map( dataKey => ({ type: EntityKeyType.ATTRIBUTE, key: dataKey.name }) ); - this.tsFields = this.entityDataSubscriptionOptions.dataKeys. - filter(dataKey => dataKey.type === DataKeyType.timeseries && !dataKey.latest).map( + this.tsFields = this.dataKeysList. + filter(dataKey => dataKey.type === DataKeyType.timeseries && + (!dataKey.aggregationType || dataKey.aggregationType === AggregationType.NONE) && !dataKey.latest).map( dataKey => ({ type: EntityKeyType.TIME_SERIES, key: dataKey.name }) ); if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { - const latestTsFields = this.entityDataSubscriptionOptions.dataKeys. - filter(dataKey => dataKey.type === DataKeyType.timeseries && dataKey.latest).map( + const latestTsFields = this.dataKeysList. + filter(dataKey => dataKey.type === DataKeyType.timeseries && dataKey.latest && + (!dataKey.aggregationType || dataKey.aggregationType === AggregationType.NONE)).map( dataKey => ({ type: EntityKeyType.TIME_SERIES, key: dataKey.name }) ); this.latestValues = this.attrFields.concat(latestTsFields); @@ -238,6 +296,19 @@ export class EntityDataSubscription { this.latestValues = this.attrFields.concat(this.tsFields); } + this.aggTsValues = this.dataKeysList. + filter(dataKey => dataKey.type === DataKeyType.timeseries && + dataKey.aggregationType && dataKey.aggregationType !== AggregationType.NONE && !dataKey.comparisonEnabled).map( + dataKey => ({ id: dataKey.index, key: dataKey.name, agg: dataKey.aggregationType }) + ); + + this.aggTsComparisonValues = this.dataKeysList. + filter(dataKey => dataKey.type === DataKeyType.timeseries && + dataKey.aggregationType && dataKey.aggregationType !== AggregationType.NONE && dataKey.comparisonEnabled).map( + dataKey => ({ id: dataKey.index, key: dataKey.name, agg: dataKey.aggregationType, + previousValueOnly: dataKey.comparisonResultType === ComparisonResultType.PREVIOUS_VALUE }) + ); + this.subscriber = new TelemetrySubscriber(this.telemetryService); this.dataCommand = new EntityDataCmd(); @@ -282,19 +353,28 @@ export class EntityDataSubscription { this.subscriber.reconnect$.subscribe(() => { if (this.started) { const targetCommand = this.entityDataSubscriptionOptions.isPaginatedDataSubscription ? this.dataCommand : this.subsCommand; - if (this.entityDataSubscriptionOptions.type === widgetType.timeseries && - !this.history && this.tsFields.length) { + if (!this.history && (this.entityDataSubscriptionOptions.type === widgetType.timeseries && this.tsFields.length || + this.aggTsValues.length > 0 && !this.isFloatingTimewindow)) { const newSubsTw = this.listener.updateRealtimeSubscription(); this.subsTw = newSubsTw; - targetCommand.tsCmd.startTs = this.subsTw.startTs; - targetCommand.tsCmd.timeWindow = this.subsTw.aggregation.timeWindow; - targetCommand.tsCmd.interval = this.subsTw.aggregation.interval; - targetCommand.tsCmd.limit = this.subsTw.aggregation.limit; - targetCommand.tsCmd.agg = this.subsTw.aggregation.type; - targetCommand.tsCmd.fetchLatestPreviousPoint = this.subsTw.aggregation.stateData; - this.dataAggregators.forEach((dataAggregator) => { - dataAggregator.reset(newSubsTw); - }); + if (this.entityDataSubscriptionOptions.type === widgetType.timeseries && this.tsFields.length) { + targetCommand.tsCmd.startTs = this.subsTw.startTs; + targetCommand.tsCmd.timeWindow = this.subsTw.aggregation.timeWindow; + targetCommand.tsCmd.interval = this.subsTw.aggregation.interval; + targetCommand.tsCmd.limit = this.subsTw.aggregation.limit; + targetCommand.tsCmd.agg = this.subsTw.aggregation.type; + targetCommand.tsCmd.fetchLatestPreviousPoint = this.subsTw.aggregation.stateData; + this.dataAggregators.forEach((dataAggregator) => { + dataAggregator.reset(newSubsTw); + }); + } + if (this.aggTsValues.length > 0 && !this.isFloatingTimewindow) { + targetCommand.aggTsCmd.startTs = this.subsTw.startTs; + targetCommand.aggTsCmd.timeWindow = this.subsTw.aggregation.timeWindow; + this.tsLatestDataAggregators.forEach((dataAggregator) => { + dataAggregator.reset(newSubsTw); + }); + } } if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { this.subscriber.setTsOffset(this.subsTw.tsOffset); @@ -359,7 +439,7 @@ export class EntityDataSubscription { entityType: null }; - const countKey = this.entityDataSubscriptionOptions.dataKeys[0]; + const countKey = this.dataKeysList[0]; let dataReceived = false; @@ -422,14 +502,9 @@ export class EntityDataSubscription { if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) { return; } - this.subsTw = this.entityDataSubscriptionOptions.subscriptionTimewindow; - this.latestTsOffset = this.entityDataSubscriptionOptions.latestTsOffset; - this.history = this.entityDataSubscriptionOptions.subscriptionTimewindow && - isObject(this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow); - this.realtime = this.entityDataSubscriptionOptions.subscriptionTimewindow && - isDefinedAndNotNull(this.entityDataSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs); + this.prepareSubscriptionTimewindow(); - this.prepareData(); + this.prepareData(true); if (this.datasourceType === DatasourceType.entity) { this.subsCommand = new EntityDataCmd(); @@ -461,7 +536,19 @@ export class EntityDataSubscription { this.started = true; } + private prepareSubscriptionTimewindow() { + this.subsTw = this.entityDataSubscriptionOptions.subscriptionTimewindow; + this.latestTsOffset = this.entityDataSubscriptionOptions.latestTsOffset; + this.history = this.entityDataSubscriptionOptions.subscriptionTimewindow && + isObject(this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow); + this.realtime = this.entityDataSubscriptionOptions.subscriptionTimewindow && + isDefinedAndNotNull(this.entityDataSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs); + this.isFloatingTimewindow = this.entityDataSubscriptionOptions.subscriptionTimewindow && + !this.entityDataSubscriptionOptions.subscriptionTimewindow.quickInterval && !this.history; + } + private prepareSubscriptionCommands(cmd: EntityDataCmd) { + let latestValuesKeys: EntityKey[] = []; if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { if (this.tsFields.length > 0) { if (this.history) { @@ -486,17 +573,40 @@ export class EntityDataSubscription { }; } } - if (this.latestValues.length > 0) { - cmd.latestCmd = { - keys: this.latestValues - }; - } + latestValuesKeys = this.latestValues; } else if (this.entityDataSubscriptionOptions.type === widgetType.latest) { - if (this.latestValues.length > 0) { - cmd.latestCmd = { - keys: this.latestValues - }; + latestValuesKeys = this.latestValues; + } + if (this.history && (this.aggTsValues.length > 0 || this.aggTsComparisonValues.length > 0)) { + for (const aggTsComparison of this.aggTsComparisonValues) { + const subscriptionDataKey = this.dataKeyByIndex(aggTsComparison.id); + const timewindowForComparison = + createTimewindowForComparison(this.subsTw, subscriptionDataKey.timeForComparison, + subscriptionDataKey.comparisonCustomIntervalValue); + aggTsComparison.previousStartTs = timewindowForComparison.fixedWindow.startTimeMs; + aggTsComparison.previousEndTs = timewindowForComparison.fixedWindow.endTimeMs; } + cmd.aggHistoryCmd = { + keys: [...this.aggTsValues, ...this.aggTsComparisonValues], + startTs: this.subsTw.fixedWindow.startTimeMs, + endTs: this.subsTw.fixedWindow.endTimeMs + }; + } else if (!this.isFloatingTimewindow && this.aggTsValues.length > 0) { + cmd.aggTsCmd = { + keys: this.aggTsValues, + startTs: this.subsTw.startTs, + timeWindow: this.subsTw.aggregation.timeWindow + }; + if (latestValuesKeys.length > 0) { + const tsKeys = this.aggTsValues.map(key => key.key); + latestValuesKeys = latestValuesKeys.filter(latestKey => latestKey.type !== EntityKeyType.TIME_SERIES + || !tsKeys.includes(latestKey.key)); + } + } + if (latestValuesKeys.length > 0) { + cmd.latestCmd = { + keys: latestValuesKeys + }; } } @@ -510,7 +620,7 @@ export class EntityDataSubscription { this.generateData(true); } - private prepareData() { + private prepareData(isUpdate: boolean) { if (this.timeseriesTimer) { clearTimeout(this.timeseriesTimer); this.timeseriesTimer = null; @@ -526,37 +636,73 @@ export class EntityDataSubscription { }); } this.dataAggregators = []; + if (this.tsLatestDataAggregators) { + this.tsLatestDataAggregators.forEach((aggregator) => { + aggregator.destroy(); + }); + } + this.tsLatestDataAggregators = []; this.resetData(); if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { - let tsKeyNames = []; + let tsKeyIds: number[]; if (this.datasourceType === DatasourceType.function) { - for (const key of Object.keys(this.dataKeys)) { - const dataKeysList = this.dataKeys[key] as Array; - dataKeysList.forEach((subscriptionDataKey) => { - if (!subscriptionDataKey.latest) { - tsKeyNames.push(`${subscriptionDataKey.name}_${subscriptionDataKey.index}`); - } - }); - } + tsKeyIds = this.dataKeysList.filter(key => !key.latest).map(key => key.index); } else { - tsKeyNames = this.tsFields ? this.tsFields.map(field => field.key) : []; + tsKeyIds = this.dataKeysList. + filter(dataKey => dataKey.type === DataKeyType.timeseries && + (!dataKey.aggregationType || dataKey.aggregationType === AggregationType.NONE) && !dataKey.latest).map( + dataKey => dataKey.index + ); } - for (let dataIndex = 0; dataIndex < this.pageData.data.length; dataIndex++) { - if (tsKeyNames.length) { - if (this.datasourceType === DatasourceType.function) { - this.dataAggregators[dataIndex] = this.createRealtimeDataAggregator(this.subsTw, tsKeyNames, - DataKeyType.function, dataIndex, this.notifyListener.bind(this)); - } else { - this.dataAggregators[dataIndex] = this.createRealtimeDataAggregator(this.subsTw, tsKeyNames, - DataKeyType.timeseries, dataIndex, this.notifyListener.bind(this)); - } + const aggKeys: AggKey[] = tsKeyIds.map(key => ({id: key, key: key + '', agg: this.subsTw.aggregation.type})); + if (aggKeys.length) { + for (let dataIndex = 0; dataIndex < this.pageData.data.length; dataIndex++) { + this.dataAggregators[dataIndex] = this.createRealtimeDataAggregator(this.subsTw, aggKeys, + false, dataIndex, this.notifyListener.bind(this)); } } } + if (this.aggTsValues && this.aggTsValues.length) { + if (!this.isFloatingTimewindow) { + const aggLatestTimewindow = deepClone(this.subsTw); + aggLatestTimewindow.aggregation.stateData = false; + aggLatestTimewindow.aggregation.interval = aggLatestTimewindow.aggregation.timeWindow; + for (let dataIndex = 0; dataIndex < this.pageData.data.length; dataIndex++) { + this.tsLatestDataAggregators[dataIndex] = this.createRealtimeDataAggregator(aggLatestTimewindow, this.aggTsValues, + true, dataIndex, this.notifyListener.bind(this)); + } + } else { + this.reportNotSupported(this.aggTsValues, isUpdate); + } + } + if (!this.history && this.aggTsComparisonValues && this.aggTsComparisonValues.length) { + this.reportNotSupported(this.aggTsComparisonValues, isUpdate); + } + } + + private reportNotSupported(keys: AggKey[], isUpdate: boolean) { + const indexedData: IndexedSubscriptionData = []; + for (const key of keys) { + indexedData[key.id] = [[0, 'Not supported!']]; + } + for (let dataIndex = 0; dataIndex < this.pageData.data.length; dataIndex++) { + this.onIndexedData(indexedData, dataIndex, true, + this.entityDataSubscriptionOptions.type === widgetType.timeseries, + (data, dataIndex1, dataKeyIndex, detectChanges, isLatest) => { + if (!this.data[dataIndex1]) { + this.data[dataIndex1] = []; + } + this.data[dataIndex1][dataKeyIndex] = data; + if (isUpdate) { + this.notifyListener(data, dataIndex1, dataKeyIndex, detectChanges, isLatest); + } + }); + } } private resetData() { + this.data = []; this.datasourceData = []; this.entityIdToDataIndex = {}; for (let dataIndex = 0; dataIndex < this.pageData.data.length; dataIndex++) { @@ -609,19 +755,18 @@ export class EntityDataSubscription { this.pageData = pageData; if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) { - this.prepareData(); + this.prepareData(false); } else if (isInitialData) { this.resetData(); } - const data: Array> = []; for (let dataIndex = 0; dataIndex < pageData.data.length; dataIndex++) { const entityData = pageData.data[dataIndex]; this.processEntityData(entityData, dataIndex, false, (data1, dataIndex1, dataKeyIndex) => { - if (!data[dataIndex1]) { - data[dataIndex1] = []; + if (!this.data[dataIndex1]) { + this.data[dataIndex1] = []; } - data[dataIndex1][dataKeyIndex] = data1; + this.data[dataIndex1][dataKeyIndex] = data1; } ); } @@ -630,7 +775,7 @@ export class EntityDataSubscription { this.entityDataResolveSubject.next( { pageData, - data, + data: this.data, datasourceIndex: this.listener.configDatasourceIndex, pageLink: this.entityDataSubscriptionOptions.pageLink } @@ -638,7 +783,7 @@ export class EntityDataSubscription { this.entityDataResolveSubject.complete(); } else { if (isInitialData || this.entityDataSubscriptionOptions.isPaginatedDataSubscription) { - this.listener.dataLoaded(pageData, data, + this.listener.dataLoaded(pageData, this.data, this.listener.configDatasourceIndex, this.entityDataSubscriptionOptions.pageLink); } if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription && isInitialData) { @@ -648,7 +793,7 @@ export class EntityDataSubscription { this.entityDataResolveSubject.next( { pageData, - data, + data: this.data, datasourceIndex: this.listener.configDatasourceIndex, pageLink: this.entityDataSubscriptionOptions.pageLink } @@ -675,27 +820,85 @@ export class EntityDataSubscription { private processEntityData(entityData: EntityData, dataIndex: number, isUpdate: boolean, dataUpdatedCb: DataUpdatedCb) { - if ((this.entityDataSubscriptionOptions.type === widgetType.latest || - this.entityDataSubscriptionOptions.type === widgetType.timeseries) && entityData.latest) { - for (const type of Object.keys(entityData.latest)) { - const subscriptionData = this.toSubscriptionData(entityData.latest[type], false); - const dataKeyType = entityKeyTypeToDataKeyType(EntityKeyType[type]); - this.onData(subscriptionData, dataKeyType, dataIndex, true, - this.entityDataSubscriptionOptions.type === widgetType.timeseries, dataUpdatedCb); + if (this.entityDataSubscriptionOptions.type === widgetType.latest || + this.entityDataSubscriptionOptions.type === widgetType.timeseries) { + if (entityData.aggLatest) { + const aggData: IndexedSubscriptionData = []; + for (const idStr of Object.keys(entityData.aggLatest)) { + const id = Number(idStr); + const dataKey = this.dataKeyByIndex(id); + const aggLatestData = entityData.aggLatest[id]; + if (dataKey.comparisonEnabled) { + const keyData = EntityDataSubscription.calculateComparisonValue(dataKey, aggLatestData); + this.onKeyData(keyData, dataKey.name, id, dataKey.type, dataIndex, true, + this.entityDataSubscriptionOptions.type === widgetType.timeseries, true, dataUpdatedCb); + } else { + aggData[id] = [[aggLatestData.current.ts, aggLatestData.current.value, aggLatestData.current.count]]; + } + } + if (Object.keys(aggData).length > 0 && this.tsLatestDataAggregators && this.tsLatestDataAggregators[dataIndex]) { + const dataAggregator = this.tsLatestDataAggregators[dataIndex]; + let prevDataCb; + if (!isUpdate) { + prevDataCb = dataAggregator.updateOnDataCb((data, detectChanges) => { + this.onIndexedData(data, dataIndex, detectChanges, + this.entityDataSubscriptionOptions.type === widgetType.timeseries, dataUpdatedCb); + }); + } + dataAggregator.onData(aggData, false, this.history, true); + if (prevDataCb) { + dataAggregator.updateOnDataCb(prevDataCb); + } + } + } + if (entityData.latest) { + for (const type of Object.keys(entityData.latest)) { + const subscriptionData = this.toSubscriptionData(entityData.latest[type], false); + const dataKeyType = entityKeyTypeToDataKeyType(EntityKeyType[type]); + if (isUpdate && EntityKeyType[type] === EntityKeyType.TIME_SERIES) { + const keys: string[] = Object.keys(subscriptionData); + const latestTsKeys = this.latestValues.filter(key => key.type === EntityKeyType.TIME_SERIES && keys.includes(key.key)); + if (latestTsKeys.length) { + const latestTsSubsciptionData: SubscriptionData = {}; + for (const latestTsKey of latestTsKeys) { + latestTsSubsciptionData[latestTsKey.key] = subscriptionData[latestTsKey.key]; + } + this.onData(latestTsSubsciptionData, dataKeyType, dataIndex, true, + this.entityDataSubscriptionOptions.type === widgetType.timeseries, dataUpdatedCb); + } + const aggTsKeys = this.aggTsValues.filter(key => keys.includes(key.key)); + if (!this.history && aggTsKeys.length && this.tsLatestDataAggregators && this.tsLatestDataAggregators[dataIndex]) { + const dataAggregator = this.tsLatestDataAggregators[dataIndex]; + const indexedData: IndexedSubscriptionData = []; + for (const aggKey of aggTsKeys) { + indexedData[aggKey.id] = subscriptionData[aggKey.key]; + } + dataAggregator.onData(indexedData, true, false, true); + } + } else { + this.onData(subscriptionData, dataKeyType, dataIndex, true, + this.entityDataSubscriptionOptions.type === widgetType.timeseries, dataUpdatedCb); + } + } } } if (this.entityDataSubscriptionOptions.type === widgetType.timeseries && entityData.timeseries) { const subscriptionData = this.toSubscriptionData(entityData.timeseries, true); if (this.dataAggregators && this.dataAggregators[dataIndex]) { const dataAggregator = this.dataAggregators[dataIndex]; + const keyNames = Object.keys(subscriptionData); + const dataKeys = this.timeseriesDataKeysByKeyNames(keyNames); + const indexedData: IndexedSubscriptionData = []; + for (const dataKey of dataKeys) { + indexedData[dataKey.index] = subscriptionData[dataKey.name]; + } let prevDataCb; if (!isUpdate) { prevDataCb = dataAggregator.updateOnDataCb((data, detectChanges) => { - this.onData(data, this.datasourceType === DatasourceType.function ? - DataKeyType.function : DataKeyType.timeseries, dataIndex, detectChanges, false, dataUpdatedCb); + this.onIndexedData(data, dataIndex, detectChanges, false, dataUpdatedCb); }); } - dataAggregator.onData({data: subscriptionData}, false, this.history, true); + dataAggregator.onData(indexedData, false, this.history, true); if (prevDataCb) { dataAggregator.updateOnDataCb(prevDataCb); } @@ -707,92 +910,109 @@ export class EntityDataSubscription { private onData(sourceData: SubscriptionData, type: DataKeyType, dataIndex: number, detectChanges: boolean, isTsLatest: boolean, dataUpdatedCb: DataUpdatedCb) { - for (const keyName of Object.keys(sourceData)) { - const keyData = sourceData[keyName]; - const key = `${keyName}_${type}${isTsLatest ? '_latest' : ''}`; - const dataKeyList = this.dataKeys[key] as Array; - for (let keyIndex = 0; dataKeyList && keyIndex < dataKeyList.length; keyIndex++) { - const datasourceKey = `${key}_${keyIndex}`; - if (this.datasourceData[dataIndex][datasourceKey].data) { - const dataKey = dataKeyList[keyIndex]; - const data: DataSet = []; - let prevSeries: [number, any]; - let prevOrigSeries: [number, any]; - let datasourceKeyData: DataSet; - let datasourceOrigKeyData: DataSet; - let update = false; - if (this.realtime && !isTsLatest) { - datasourceKeyData = []; - datasourceOrigKeyData = []; - } else { - datasourceKeyData = this.datasourceData[dataIndex][datasourceKey].data; - datasourceOrigKeyData = this.datasourceOrigData[dataIndex][datasourceKey].data; - } - if (datasourceKeyData.length > 0) { - prevSeries = datasourceKeyData[datasourceKeyData.length - 1]; - prevOrigSeries = datasourceOrigKeyData[datasourceOrigKeyData.length - 1]; - } else { - prevSeries = [0, 0]; - prevOrigSeries = [0, 0]; - } - this.datasourceOrigData[dataIndex][datasourceKey].data = []; - if (this.entityDataSubscriptionOptions.type === widgetType.timeseries && !isTsLatest) { - keyData.forEach((keySeries) => { - let series = keySeries; - const time = series[0]; - this.datasourceOrigData[dataIndex][datasourceKey].data.push(series); - let value = this.convertValue(series[1]); - if (dataKey.postFunc) { - value = dataKey.postFunc(time, value, prevSeries[1], prevOrigSeries[0], prevOrigSeries[1]); - } - prevOrigSeries = series; - series = [time, value]; - data.push(series); - prevSeries = series; - }); - update = true; - } else if (this.entityDataSubscriptionOptions.type === widgetType.latest || isTsLatest) { - if (keyData.length > 0) { - let series = keyData[0]; - const time = series[0]; - this.datasourceOrigData[dataIndex][datasourceKey].data.push(series); - let value = this.convertValue(series[1]); - if (dataKey.postFunc) { - value = dataKey.postFunc(time, value, prevSeries[1], prevOrigSeries[0], prevOrigSeries[1]); - } - series = [time, value]; - data.push(series); - } - update = true; - } - if (update) { - this.datasourceData[dataIndex][datasourceKey].data = data; - dataUpdatedCb(this.datasourceData[dataIndex][datasourceKey], dataIndex, dataKey.index, detectChanges, isTsLatest); - } - } + for (const key of Object.keys(sourceData)) { + const keyData = sourceData[key]; + this.onKeyData(keyData, key, 0, type, + dataIndex, detectChanges, isTsLatest, false, dataUpdatedCb); + } + } + + private onIndexedData(sourceData: IndexedSubscriptionData, dataIndex: number, detectChanges: boolean, + isTsLatest: boolean, dataUpdatedCb: DataUpdatedCb) { + for (const indexStr of Object.keys(sourceData)) { + const id = Number(indexStr); + const dataKey = this.dataKeyByIndex(id); + const isAggLatest = dataKey.aggregationType && dataKey.aggregationType !== AggregationType.NONE; + const keyData = sourceData[id]; + let keyName = dataKey.name; + if (dataKey.type === DataKeyType.function) { + keyName += `_${dataKey.index}`; } + this.onKeyData(keyData, keyName, id, dataKey.type, + dataIndex, detectChanges, isTsLatest, isAggLatest, dataUpdatedCb); } } - private convertValue(val: string): any { - if (val && isNumeric(val) && Number(val).toString() === val) { - return Number(val); + private onKeyData(keyData: [number, any, number?][], keyName: string, id: number, type: DataKeyType, + dataIndex: number, detectChanges: boolean, + isTsLatest: boolean, isAggLatest: boolean, dataUpdatedCb: DataUpdatedCb) { + const keyIdSuffix = isAggLatest ? `_${id}` : ''; + const key = `${keyName}_${type}${keyIdSuffix}${isTsLatest ? '_latest' : ''}`; + const dataKeyList = this.dataKeys[key] as Array; + for (let keyIndex = 0; dataKeyList && keyIndex < dataKeyList.length; keyIndex++) { + const datasourceKey = `${key}_${keyIndex}`; + if (this.datasourceData[dataIndex][datasourceKey].data) { + const dataKey = dataKeyList[keyIndex]; + const data: DataSet = []; + let prevSeries: [number, any]; + let prevOrigSeries: [number, any]; + let datasourceKeyData: DataSet; + let datasourceOrigKeyData: DataSet; + let update = false; + if (this.realtime && !isTsLatest) { + datasourceKeyData = []; + datasourceOrigKeyData = []; + } else { + datasourceKeyData = this.datasourceData[dataIndex][datasourceKey].data; + datasourceOrigKeyData = this.datasourceOrigData[dataIndex][datasourceKey].data; + } + if (datasourceKeyData.length > 0) { + prevSeries = datasourceKeyData[datasourceKeyData.length - 1]; + prevOrigSeries = datasourceOrigKeyData[datasourceOrigKeyData.length - 1]; + } else { + prevSeries = [0, 0]; + prevOrigSeries = [0, 0]; + } + this.datasourceOrigData[dataIndex][datasourceKey].data = []; + if (this.entityDataSubscriptionOptions.type === widgetType.timeseries && !isTsLatest) { + keyData.forEach((keySeries) => { + let series = keySeries; + const time = series[0]; + this.datasourceOrigData[dataIndex][datasourceKey].data.push([series[0], series[1]]); + let value = EntityDataSubscription.convertValue(series[1]); + if (dataKey.postFunc) { + value = dataKey.postFunc(time, value, prevSeries[1], prevOrigSeries[0], prevOrigSeries[1]); + } + prevOrigSeries = [series[0], series[1]]; + series = [series[0], value]; + data.push([series[0], series[1]]); + prevSeries = [series[0], series[1]]; + }); + update = true; + } else if (this.entityDataSubscriptionOptions.type === widgetType.latest || isTsLatest) { + if (keyData.length > 0) { + let series = keyData[0]; + const time = series[0]; + this.datasourceOrigData[dataIndex][datasourceKey].data.push([series[0], series[1]]); + let value = EntityDataSubscription.convertValue(series[1]); + if (dataKey.postFunc) { + value = dataKey.postFunc(time, value, prevSeries[1], prevOrigSeries[0], prevOrigSeries[1]); + } + series = [time, value]; + data.push([series[0], series[1]]); + } + update = true; + } + if (update) { + this.datasourceData[dataIndex][datasourceKey].data = data; + dataUpdatedCb(this.datasourceData[dataIndex][datasourceKey], dataIndex, dataKey.index, detectChanges, isTsLatest); + } + } } - return val; } private toSubscriptionData(sourceData: {[key: string]: TsValue | TsValue[]}, isTs: boolean): SubscriptionData { const subsData: SubscriptionData = {}; for (const keyName of Object.keys(sourceData)) { const values = sourceData[keyName]; - const dataSet: [number, any][] = []; + const dataSet: [number, any, number?][] = []; if (isTs) { (values as TsValue[]).forEach((keySeries) => { - dataSet.push([keySeries.ts, keySeries.value]); + dataSet.push([keySeries.ts, keySeries.value, keySeries.count]); }); } else { const tsValue = values as TsValue; - dataSet.push([tsValue.ts, tsValue.value]); + dataSet.push([tsValue.ts, tsValue.value, tsValue.count]); } subsData[keyName] = dataSet; } @@ -800,21 +1020,37 @@ export class EntityDataSubscription { } private createRealtimeDataAggregator(subsTw: SubscriptionTimewindow, - tsKeyNames: Array, - dataKeyType: DataKeyType, + tsKeys: Array, + isLatestDataAgg: boolean, dataIndex: number, dataUpdatedCb: DataUpdatedCb): DataAggregator { return new DataAggregator( (data, detectChanges) => { - this.onData(data, dataKeyType, dataIndex, detectChanges, false, dataUpdatedCb); + this.onIndexedData(data, dataIndex, detectChanges, + isLatestDataAgg && (this.entityDataSubscriptionOptions.type === widgetType.timeseries), dataUpdatedCb); }, - tsKeyNames, + tsKeys, + isLatestDataAgg, subsTw, this.utils, - this.entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick + this.entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick || isLatestDataAgg ); } + private dataKeyByIndex(index: number): SubscriptionDataKey { + return this.dataKeysList.find(key => key.index === index); + } + + private timeseriesDataKeysByKeyNames(keyNames: string[]): SubscriptionDataKey[] { + const result: SubscriptionDataKey[] = []; + for (const keyName of keyNames) { + const key = `${keyName}_${DataKeyType.timeseries}`; + const dataKeyList = this.dataKeys[key] as Array; + result.push(...dataKeyList); + } + return result; + } + private generateSeries(dataKey: SubscriptionDataKey, startTime: number, endTime: number): [number, any][] { const data: [number, any][] = []; let prevSeries: [number, any]; @@ -893,9 +1129,7 @@ export class EntityDataSubscription { let startTime: number; let endTime: number; let delta: number; - const generatedData: SubscriptionDataHolder = { - data: {} - }; + const generatedData: IndexedSubscriptionData = []; if (!this.history) { delta = Math.floor(this.tickElapsed / this.frequency); } @@ -928,7 +1162,7 @@ export class EntityDataSubscription { endTime = Math.min(currentTime, endTime); } } - generatedData.data[`${dataKey.name}_${dataKey.index}`] = this.generateSeries(dataKey, startTime, endTime); + generatedData[dataKey.index] = this.generateSeries(dataKey, startTime, endTime); } if (this.dataAggregators && this.dataAggregators.length) { this.dataAggregators[0].onData(generatedData, true, this.history, detectChanges); diff --git a/ui-ngx/src/app/core/api/entity-data.service.ts b/ui-ngx/src/app/core/api/entity-data.service.ts index 3d3112f007..6d8ea94dc0 100644 --- a/ui-ngx/src/app/core/api/entity-data.service.ts +++ b/ui-ngx/src/app/core/api/entity-data.service.ts @@ -31,6 +31,7 @@ import { Observable, of } from 'rxjs'; export interface EntityDataListener { subscriptionType: widgetType; + useTimewindow?: boolean; subscriptionTimewindow?: SubscriptionTimewindow; latestTsOffset?: number; configDatasource: Datasource; @@ -73,6 +74,21 @@ export class EntityDataService { } } + private static toSubscriptionDataKey(dataKey: DataKey, latest: boolean): SubscriptionDataKey { + return { + name: dataKey.name, + type: dataKey.type, + aggregationType: dataKey.aggregationType, + comparisonEnabled: dataKey.comparisonEnabled, + timeForComparison: dataKey.timeForComparison, + comparisonCustomIntervalValue: dataKey.comparisonCustomIntervalValue, + comparisonResultType: dataKey.comparisonResultType, + funcBody: dataKey.funcBody, + postFuncBody: dataKey.postFuncBody, + latest + }; + } + public prepareSubscription(listener: EntityDataListener, ignoreDataUpdateOnIntervalTick = false): Observable { const datasource = listener.configDatasource; @@ -93,10 +109,10 @@ export class EntityDataService { public startSubscription(listener: EntityDataListener) { if (listener.subscription) { - if (listener.subscriptionType === widgetType.timeseries) { + if (listener.useTimewindow) { listener.subscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow); - listener.subscriptionOptions.latestTsOffset = listener.latestTsOffset; - } else if (listener.subscriptionType === widgetType.latest) { + } + if (listener.subscriptionType === widgetType.timeseries || listener.subscriptionType === widgetType.latest) { listener.subscriptionOptions.latestTsOffset = listener.latestTsOffset; } listener.subscription.start(); @@ -122,10 +138,10 @@ export class EntityDataService { return of(null); } listener.subscription = new EntityDataSubscription(listener, this.telemetryService, this.utils); - if (listener.subscriptionType === widgetType.timeseries) { + if (listener.useTimewindow) { listener.subscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow); - listener.subscriptionOptions.latestTsOffset = listener.latestTsOffset; - } else if (listener.subscriptionType === widgetType.latest) { + } + if (listener.subscriptionType === widgetType.timeseries || listener.subscriptionType === widgetType.latest) { listener.subscriptionOptions.latestTsOffset = listener.latestTsOffset; } return listener.subscription.subscribe(); @@ -146,11 +162,11 @@ export class EntityDataService { ignoreDataUpdateOnIntervalTick: boolean): EntityDataSubscriptionOptions { const subscriptionDataKeys: Array = []; datasource.dataKeys.forEach((dataKey) => { - subscriptionDataKeys.push(this.toSubscriptionDataKey(dataKey, false)); + subscriptionDataKeys.push(EntityDataService.toSubscriptionDataKey(dataKey, false)); }); if (datasource.latestDataKeys) { datasource.latestDataKeys.forEach((dataKey) => { - subscriptionDataKeys.push(this.toSubscriptionDataKey(dataKey, true)); + subscriptionDataKeys.push(EntityDataService.toSubscriptionDataKey(dataKey, true)); }); } const entityDataSubscriptionOptions: EntityDataSubscriptionOptions = { @@ -171,14 +187,4 @@ export class EntityDataService { entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick = ignoreDataUpdateOnIntervalTick; return entityDataSubscriptionOptions; } - - private toSubscriptionDataKey(dataKey: DataKey, latest: boolean): SubscriptionDataKey { - return { - name: dataKey.name, - type: dataKey.type, - funcBody: dataKey.funcBody, - postFuncBody: dataKey.postFuncBody, - latest - }; - } } diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index 41aeb5dd14..a01e731b8d 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -28,6 +28,7 @@ import { DataSetHolder, Datasource, DatasourceData, + datasourcesHasAggregation, DatasourceType, LegendConfig, LegendData, @@ -95,6 +96,7 @@ export class WidgetSubscription implements IWidgetSubscription { timezone: string; subscriptionTimewindow: SubscriptionTimewindow; useDashboardTimewindow: boolean; + useTimewindow: boolean; tsOffset = 0; hasDataPageLink: boolean; @@ -200,6 +202,7 @@ export class WidgetSubscription implements IWidgetSubscription { this.originalTimewindow = null; this.timeWindow = {}; this.useDashboardTimewindow = options.useDashboardTimewindow; + this.useTimewindow = true; if (this.useDashboardTimewindow) { this.timeWindowConfig = deepClone(options.dashboardTimewindow); } else { @@ -245,15 +248,16 @@ export class WidgetSubscription implements IWidgetSubscription { this.timeWindow = {}; this.useDashboardTimewindow = options.useDashboardTimewindow; this.stateData = options.stateData; - if (this.type === widgetType.latest) { - this.timezone = options.dashboardTimewindow.timezone; - this.updateTsOffset(); - } + this.useTimewindow = this.type === widgetType.timeseries || datasourcesHasAggregation(this.configuredDatasources); if (this.useDashboardTimewindow) { this.timeWindowConfig = deepClone(options.dashboardTimewindow); } else { this.timeWindowConfig = deepClone(options.timeWindowConfig); } + if (this.type === widgetType.latest) { + this.timezone = this.useTimewindow ? this.timeWindowConfig.timezone : options.dashboardTimewindow.timezone; + this.updateTsOffset(); + } this.subscriptionTimewindow = null; this.comparisonEnabled = options.comparisonEnabled && isHistoryTypeTimewindow(this.timeWindowConfig); @@ -443,6 +447,7 @@ export class WidgetSubscription implements IWidgetSubscription { const resolveResultObservables = this.configuredDatasources.map((datasource, index) => { const listener: EntityDataListener = { subscriptionType: this.type, + useTimewindow: this.useTimewindow, configDatasource: datasource, configDatasourceIndex: index, dataLoaded: (pageData, data1, datasourceIndex, pageLink) => { @@ -626,22 +631,31 @@ export class WidgetSubscription implements IWidgetSubscription { } onDashboardTimewindowChanged(newDashboardTimewindow: Timewindow) { - if (this.type === widgetType.timeseries || this.type === widgetType.alarm) { + let doUpdate = false; + let isTimewindowTypeChanged = false; + if (this.useTimewindow) { if (this.useDashboardTimewindow) { + if (this.type === widgetType.latest) { + if (newDashboardTimewindow && this.timezone !== newDashboardTimewindow.timezone) { + this.timezone = newDashboardTimewindow.timezone; + doUpdate = this.updateTsOffset(); + } + } if (!isEqual(this.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) { - const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, newDashboardTimewindow); + isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, newDashboardTimewindow); this.timeWindowConfig = deepClone(newDashboardTimewindow); - this.update(isTimewindowTypeChanged); + doUpdate = true; } } } else if (this.type === widgetType.latest) { if (newDashboardTimewindow && this.timezone !== newDashboardTimewindow.timezone) { this.timezone = newDashboardTimewindow.timezone; - if (this.updateTsOffset()) { - this.update(); - } + doUpdate = this.updateTsOffset(); } } + if (doUpdate) { + this.update(isTimewindowTypeChanged); + } } updateDataVisibility(index: number): void { @@ -660,6 +674,12 @@ export class WidgetSubscription implements IWidgetSubscription { updateTimewindowConfig(newTimewindow: Timewindow): void { if (!this.useDashboardTimewindow) { + if (this.type === widgetType.latest) { + if (newTimewindow && this.timezone !== newTimewindow.timezone) { + this.timezone = newTimewindow.timezone; + this.updateTsOffset(); + } + } const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, newTimewindow); this.timeWindowConfig = newTimewindow; this.update(isTimewindowTypeChanged); @@ -874,11 +894,12 @@ export class WidgetSubscription implements IWidgetSubscription { } const datasource = this.configuredDatasources[datasourceIndex]; if (datasource) { - if (this.type === widgetType.timeseries && this.timeWindowConfig) { + if (this.useTimewindow && this.timeWindowConfig) { this.updateRealtimeSubscription(); } entityDataListener = { subscriptionType: this.type, + useTimewindow: this.useTimewindow, configDatasource: datasource, configDatasourceIndex: datasourceIndex, subscriptionTimewindow: this.subscriptionTimewindow, @@ -940,7 +961,7 @@ export class WidgetSubscription implements IWidgetSubscription { private updateDataTimewindow() { if (!this.hasDataPageLink) { - if (this.type === widgetType.timeseries && this.timeWindowConfig) { + if (this.useTimewindow && this.timeWindowConfig) { this.updateRealtimeSubscription(); if (this.comparisonEnabled) { this.updateSubscriptionForComparison(); @@ -952,11 +973,11 @@ export class WidgetSubscription implements IWidgetSubscription { private dataSubscribe() { this.updateDataTimewindow(); if (!this.hasDataPageLink) { - if (this.type === widgetType.timeseries && this.timeWindowConfig && this.subscriptionTimewindow.fixedWindow) { + if (this.useTimewindow && this.timeWindowConfig && this.subscriptionTimewindow.fixedWindow) { this.onDataUpdated(); } const forceUpdate = !this.datasources.length; - const notifyDataLoaded = !this.entityDataListeners.filter((listener) => listener.subscription ? true : false).length; + const notifyDataLoaded = !this.entityDataListeners.filter((listener) => !!listener.subscription).length; this.entityDataListeners.forEach((listener) => { if (this.comparisonEnabled && listener.configDatasource.isAdditional) { listener.subscriptionTimewindow = this.timewindowForComparison; diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index c7b1a283d3..172e4d5ac9 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -268,7 +268,9 @@ export class AuthService { public defaultUrl(isAuthenticated: boolean, authState?: AuthState, path?: string, params?: any): UrlTree { let result: UrlTree = null; if (isAuthenticated) { - if (!path || path === 'login' || this.forceDefaultPlace(authState, path, params)) { + if (authState.authUser.authority === Authority.PRE_VERIFICATION_TOKEN) { + result = this.router.parseUrl('login/mfa'); + } else if (!path || path === 'login' || this.forceDefaultPlace(authState, path, params)) { if (this.redirectUrl) { const redirectUrl = this.redirectUrl; this.redirectUrl = null; diff --git a/ui-ngx/src/app/core/services/dashboard-utils.service.ts b/ui-ngx/src/app/core/services/dashboard-utils.service.ts index c157ca869e..90238ebaba 100644 --- a/ui-ngx/src/app/core/services/dashboard-utils.service.ts +++ b/ui-ngx/src/app/core/services/dashboard-utils.service.ts @@ -34,6 +34,7 @@ import { Datasource, DatasourceType, Widget, widgetType } from '@app/shared/mode import { EntityType } from '@shared/models/entity-type.models'; import { AliasFilterType, EntityAlias, EntityAliasFilter } from '@app/shared/models/alias.models'; import { EntityId } from '@app/shared/models/id/entity-id'; +import { initModelFromDefaultTimewindow } from '@shared/models/time/time.models'; @Injectable({ providedIn: 'root' @@ -215,6 +216,9 @@ export class DashboardUtilsService { delete datasource.deviceAliasId; } }); + if (widget.type === widgetType.latest) { + widget.config.timewindow = initModelFromDefaultTimewindow(widget.config.timewindow, true, this.timeService); + } // Temp workaround if (widget.isSystemType && widget.bundleAlias === 'charts' && widget.typeAlias === 'timeseries') { widget.typeAlias = 'basic_timeseries'; diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index 4ebaccb774..c145794163 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -1062,7 +1062,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC dataKeys: config.alarmSource.dataKeys || [] }; } - const newWidget: Widget = { + let newWidget: Widget = { isSystemType: widget.isSystemType, bundleAlias: widget.bundleAlias, typeAlias: widgetTypeInfo.alias, @@ -1076,6 +1076,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC row: 0, col: 0 }; + newWidget = this.dashboardUtils.validateAndUpdateWidget(newWidget); if (widgetTypeInfo.typeParameters.useCustomDatasources) { this.addWidgetToDashboard(newWidget); } else { diff --git a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.scss b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.scss index 6f9fcc2102..eefcbb5044 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.scss +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.scss @@ -19,6 +19,11 @@ width: 100%; height: 100%; display: block; + + mat-drawer-container { + overflow: clip; + } + .tb-entity-table { .tb-entity-table-content { width: 100%; diff --git a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts index 5dfa528e9a..074381f5de 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts @@ -305,7 +305,6 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa this.textSearchMode = true; this.pageLink.textSearch = decodeURI(params.textSearch); } else { - this.textSearchMode = false; this.pageLink.textSearch = null; } this.updateData(); diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index d195137509..9468289df8 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -166,6 +166,10 @@ import { EntityTypesVersionLoadComponent } from '@home/components/vc/entity-type import { ComplexVersionLoadComponent } from '@home/components/vc/complex-version-load.component'; import { RemoveOtherEntitiesConfirmComponent } from '@home/components/vc/remove-other-entities-confirm.component'; import { AutoCommitSettingsComponent } from '@home/components/vc/auto-commit-settings.component'; +import { RateLimitsListComponent } from '@home/components/profile/tenant/rate-limits/rate-limits-list.component'; +import { RateLimitsComponent } from '@home/components/profile/tenant/rate-limits/rate-limits.component'; +import { RateLimitsTextComponent } from '@home/components/profile/tenant/rate-limits/rate-limits-text.component'; +import { RateLimitsDetailsDialogComponent } from '@home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component'; @NgModule({ declarations: @@ -302,7 +306,11 @@ import { AutoCommitSettingsComponent } from '@home/components/vc/auto-commit-set EntityTypesVersionLoadComponent, ComplexVersionLoadComponent, RemoveOtherEntitiesConfirmComponent, - AutoCommitSettingsComponent + AutoCommitSettingsComponent, + RateLimitsListComponent, + RateLimitsComponent, + RateLimitsTextComponent, + RateLimitsDetailsDialogComponent ], imports: [ CommonModule, @@ -432,7 +440,11 @@ import { AutoCommitSettingsComponent } from '@home/components/vc/auto-commit-set EntityTypesVersionLoadComponent, ComplexVersionLoadComponent, RemoveOtherEntitiesConfirmComponent, - AutoCommitSettingsComponent + AutoCommitSettingsComponent, + RateLimitsListComponent, + RateLimitsComponent, + RateLimitsTextComponent, + RateLimitsDetailsDialogComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index 0e1553806a..a3f285bece 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -418,38 +418,24 @@
- Rate limits + + {{ 'tenant-profile.rate-limits.rate-limits' | translate }} +
- - tenant-profile.transport-tenant-msg-rate-limit - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - - - tenant-profile.transport-device-msg-rate-limit - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - + + + +
- - tenant-profile.transport-tenant-telemetry-msg-rate-limit - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - - - tenant-profile.transport-device-telemetry-msg-rate-limit - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - + + + +
@@ -459,66 +445,36 @@
- - tenant-profile.transport-tenant-telemetry-data-points-rate-limit - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - - - tenant-profile.transport-device-telemetry-data-points-rate-limit - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - + + + +
- - tenant-profile.tenant-rest-limits - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - - - tenant-profile.customer-rest-limits - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - + + + +
- - tenant-profile.tenant-entity-export-rate-limit - - - - tenant-profile.tenant-entity-import-rate-limit - - + + + +
- - tenant-profile.ws-limit-updates-per-session - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - - - tenant-profile.cassandra-tenant-limits-configuration - - - {{ 'tenant-profile.incorrect-pattern-for-rate-limits' | translate}} - - + + + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts index 65f030a3a6..9649966bd1 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts @@ -21,6 +21,7 @@ import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { DefaultTenantProfileConfiguration, TenantProfileConfiguration } from '@shared/models/tenant.model'; import { isDefinedAndNotNull } from '@core/utils'; +import { RateLimitsType } from './rate-limits/rate-limits.models'; @Component({ selector: 'tb-default-tenant-profile-configuration', @@ -35,7 +36,6 @@ import { isDefinedAndNotNull } from '@core/utils'; export class DefaultTenantProfileConfigurationComponent implements ControlValueAccessor, OnInit { defaultTenantProfileConfigurationFormGroup: FormGroup; - rateLimitsPattern = '([1-9]\\d*:[1-9]\\d*)(,[1-9]\\d*:[1-9]\\d*)*'; private requiredValue: boolean; get required(): boolean { @@ -49,6 +49,8 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA @Input() disabled: boolean; + rateLimitsType = RateLimitsType; + private propagateChange = (v: any) => { }; constructor(private store: Store, @@ -62,12 +64,12 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA maxRuleChains: [null, [Validators.required, Validators.min(0)]], maxResourcesInBytes: [null, [Validators.required, Validators.min(0)]], maxOtaPackagesInBytes: [null, [Validators.required, Validators.min(0)]], - transportTenantMsgRateLimit: [null, [Validators.pattern(this.rateLimitsPattern)]], - transportTenantTelemetryMsgRateLimit: [null, [Validators.pattern(this.rateLimitsPattern)]], - transportTenantTelemetryDataPointsRateLimit: [null, [Validators.pattern(this.rateLimitsPattern)]], - transportDeviceMsgRateLimit: [null, [Validators.pattern(this.rateLimitsPattern)]], - transportDeviceTelemetryMsgRateLimit: [null, [Validators.pattern(this.rateLimitsPattern)]], - transportDeviceTelemetryDataPointsRateLimit: [null, [Validators.pattern(this.rateLimitsPattern)]], + transportTenantMsgRateLimit: [null, []], + transportTenantTelemetryMsgRateLimit: [null, []], + transportTenantTelemetryDataPointsRateLimit: [null, []], + transportDeviceMsgRateLimit: [null, []], + transportDeviceTelemetryMsgRateLimit: [null, []], + transportDeviceTelemetryDataPointsRateLimit: [null, []], tenantEntityExportRateLimit: [null, []], tenantEntityImportRateLimit: [null, []], maxTransportMessages: [null, [Validators.required, Validators.min(0)]], @@ -82,8 +84,8 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA defaultStorageTtlDays: [null, [Validators.required, Validators.min(0)]], alarmsTtlDays: [null, [Validators.required, Validators.min(0)]], rpcTtlDays: [null, [Validators.required, Validators.min(0)]], - tenantServerRestLimitsConfiguration: [null, [Validators.pattern(this.rateLimitsPattern)]], - customerServerRestLimitsConfiguration: [null, [Validators.pattern(this.rateLimitsPattern)]], + tenantServerRestLimitsConfiguration: [null, []], + customerServerRestLimitsConfiguration: [null, []], maxWsSessionsPerTenant: [null, [Validators.min(0)]], maxWsSessionsPerCustomer: [null, [Validators.min(0)]], maxWsSessionsPerRegularUser: [null, [Validators.min(0)]], @@ -93,8 +95,8 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA maxWsSubscriptionsPerCustomer: [null, [Validators.min(0)]], maxWsSubscriptionsPerRegularUser: [null, [Validators.min(0)]], maxWsSubscriptionsPerPublicUser: [null, [Validators.min(0)]], - wsUpdatesPerSessionRateLimit: [null, [Validators.pattern(this.rateLimitsPattern)]], - cassandraQueryTenantRateLimitsConfiguration: [null, [Validators.pattern(this.rateLimitsPattern)]] + wsUpdatesPerSessionRateLimit: [null, []], + cassandraQueryTenantRateLimitsConfiguration: [null, []] }); this.defaultTenantProfileConfigurationFormGroup.valueChanges.subscribe(() => { this.updateModel(); diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.html new file mode 100644 index 0000000000..5963c66cc0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.html @@ -0,0 +1,43 @@ + +
+ +

{{ title | translate }}

+ + +
+
+ +
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.ts new file mode 100644 index 0000000000..4818304491 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component.ts @@ -0,0 +1,59 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { Router } from '@angular/router'; +import { DialogComponent } from '@app/shared/components/dialog.component'; + +export interface RateLimitsDetailsDialogData { + rateLimits: string; + title: string; + readonly: boolean; +} + +@Component({ + templateUrl: './rate-limits-details-dialog.component.html' +}) +export class RateLimitsDetailsDialogComponent extends DialogComponent { + + editDetailsFormGroup: FormGroup; + + rateLimits: string = this.data.rateLimits; + + title: string = this.data.title; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: RateLimitsDetailsDialogData, + public dialogRef: MatDialogRef, + private fb: FormBuilder) { + super(store, router, dialogRef); + this.editDetailsFormGroup = this.fb.group({ + rateLimits: [this.rateLimits, []] + }); + if (this.data.readonly) { + this.editDetailsFormGroup.disable(); + } + } + + save(): void { + this.dialogRef.close(this.editDetailsFormGroup.get('rateLimits').value); + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.html new file mode 100644 index 0000000000..ba51cbc358 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.html @@ -0,0 +1,66 @@ + +
+
+
+ tenant-profile.rate-limits.but-less-than +
+
+ + tenant-profile.rate-limits.number-of-messages + + + {{ 'tenant-profile.rate-limits.number-of-messages-required' | translate }} + + + {{ 'tenant-profile.rate-limits.number-of-messages-min' | translate }} + + + + tenant-profile.rate-limits.per-seconds + + + {{ 'tenant-profile.rate-limits.per-seconds-required' | translate }} + + + {{ 'tenant-profile.rate-limits.per-seconds-min' | translate }} + + + +
+
+ +
+ tenant-profile.rate-limits.preview + +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.scss b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.scss new file mode 100644 index 0000000000..2a53096c17 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.scss @@ -0,0 +1,57 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import "../../../../../../../scss/constants"; + +:host { + .tb-rate-limits-form { + @media #{$mat-gt-sm} { + min-width: 600px; + } + } + + .tb-rate-limits-preview { + margin-top: 1.5em; + span { + padding-left: 1em; + } + tb-rate-limits-text { + border: 1px groove rgba(0, 0, 0, .25); + border-radius: 4px; + padding: 1em; + } + } + + .tb-rate-limits-operation { + font-size: 12px; + color: rgba(0,0,0,.54); + margin-bottom: 16px; + } + + .tb-rate-limits-button { + margin-top: 0.5em; + } +} + +:host ::ng-deep { + mat-form-field { + .mat-form-field-wrapper { + padding-bottom: 1em; + } + .mat-form-field-infix { + width: 100%; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.ts new file mode 100644 index 0000000000..e6ad1c5b20 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.ts @@ -0,0 +1,152 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; +import { + ControlValueAccessor, + FormArray, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + Validators +} from '@angular/forms'; +import { Subject, Subscription } from 'rxjs'; +import { RateLimits, rateLimitsArrayToString, stringToRateLimitsArray } from './rate-limits.models'; +import { isDefinedAndNotNull } from '@core/utils'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'tb-rate-limits-list', + templateUrl: './rate-limits-list.component.html', + styleUrls: ['./rate-limits-list.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => RateLimitsListComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => RateLimitsListComponent), + multi: true + } + ] +}) +export class RateLimitsListComponent implements ControlValueAccessor, Validator, OnInit, OnDestroy { + + @Input() disabled: boolean; + + rateLimitsListFormGroup: FormGroup; + + rateLimitsArray: Array; + + private valueChangeSubscription: Subscription = null; + private destroy$ = new Subject(); + private propagateChange = (v: any) => { }; + + constructor(private fb: FormBuilder) {} + + ngOnInit(): void { + this.rateLimitsListFormGroup = this.fb.group({ + rateLimits: this.fb.array([]) + }); + this.valueChangeSubscription = this.rateLimitsListFormGroup.valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe((value) => { + this.updateView(value?.rateLimits); + } + ); + } + + public removeRateLimits(index: number) { + (this.rateLimitsListFormGroup.get('rateLimits') as FormArray).removeAt(index); + } + + public addRateLimits() { + this.rateLimitsFormArray.push(this.fb.group({ + value: [null, [Validators.required]], + time: [null, [Validators.required]] + })); + } + + get rateLimitsFormArray(): FormArray { + return this.rateLimitsListFormGroup.get('rateLimits') as FormArray; + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState?(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.rateLimitsListFormGroup.disable({emitEvent: false}); + } else { + this.rateLimitsListFormGroup.enable({emitEvent: false}); + } + } + + validate(): ValidationErrors | null { + return this.rateLimitsListFormGroup.valid ? null : { + rateLimitsList: {valid: false} + }; + } + + writeValue(rateLimits: string) { + const rateLimitsControls: Array = []; + if (rateLimits) { + const rateLimitsArray = rateLimits.split(','); + for (let i = 0; i < rateLimitsArray.length; i++) { + const [value, time] = rateLimitsArray[i].split(':'); + const rateLimitsControl = this.fb.group({ + value: [value, [Validators.required]], + time: [time, [Validators.required]] + }); + if (this.disabled) { + rateLimitsControl.disable(); + } + rateLimitsControls.push(rateLimitsControl); + } + } + this.rateLimitsListFormGroup.setControl('rateLimits', this.fb.array(rateLimitsControls), {emitEvent: false}); + this.rateLimitsArray = stringToRateLimitsArray(rateLimits); + } + + updateView(rateLimitsArray: Array) { + if (rateLimitsArray.length > 0) { + const notNullRateLimits = rateLimitsArray.filter(rateLimits => + isDefinedAndNotNull(rateLimits.value) && isDefinedAndNotNull(rateLimits.time) + ); + const rateLimitsString = rateLimitsArrayToString(notNullRateLimits); + this.propagateChange(rateLimitsString); + this.rateLimitsArray = stringToRateLimitsArray(rateLimitsString); + } else { + this.propagateChange(null); + this.rateLimitsArray = null; + } + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.html new file mode 100644 index 0000000000..e154a7ce54 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.html @@ -0,0 +1,18 @@ + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.scss b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.scss new file mode 100644 index 0000000000..b78f1033bd --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.scss @@ -0,0 +1,41 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .tb-rate-limits-text { + overflow: hidden; + + &.disabled { + opacity: 0.7; + } + } +} + +:host ::ng-deep { + .tb-rate-limits-text { + span { + font-size: 14px; + line-height: 1.8em; + } + .tb-rate-limits-value { + font-weight: bold; + border: 1px groove rgba(0, 0, 0, .25); + border-radius: 4px; + padding-left: 4px; + padding-right: 4px; + color: #305680; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.ts new file mode 100644 index 0000000000..9081acc208 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-text.component.ts @@ -0,0 +1,55 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { RateLimits, rateLimitsArrayToHtml } from './rate-limits.models'; + +@Component({ + selector: 'tb-rate-limits-text', + templateUrl: './rate-limits-text.component.html', + styleUrls: ['./rate-limits-text.component.scss'] +}) +export class RateLimitsTextComponent implements OnChanges { + + @Input() + rateLimitsArray: Array; + + @Input() + disabled: boolean; + + rateLimitsText: string; + + constructor(private translate: TranslateService) { + } + + ngOnChanges(changes: SimpleChanges): void { + for (const propName of Object.keys(changes)) { + if (propName === 'rateLimitsArray') { + const change = changes[propName]; + this.updateView(change.currentValue); + } + } + } + + private updateView(value: Array): void { + if (value?.length) { + this.rateLimitsText = rateLimitsArrayToHtml(this.translate, value); + } else { + this.rateLimitsText = this.translate.instant('tenant-profile.rate-limits.not-set'); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.html new file mode 100644 index 0000000000..0ab20fc533 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.html @@ -0,0 +1,33 @@ + +
+
+ {{ label | translate }} +
+ +
+
+ +
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.scss b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.scss new file mode 100644 index 0000000000..1d7e817839 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.scss @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + padding: 12px 0 12px 0; + + .fieldset-element { + cursor: pointer; + padding: 0.5em; + border: 1px groove rgba(0, 0, 0, 0.25); + border-radius: 4px; + width: 100%; + + .legend-element { + color: rgba(0, 0, 0, 0.54); + font-size: 12px; + } + } + + .tb-rate-limits-button { + margin-top: 0.5em; + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.ts new file mode 100644 index 0000000000..77eb7ee1b3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.component.ts @@ -0,0 +1,149 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormControl, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + Validator +} from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { + RateLimitsDetailsDialogComponent, + RateLimitsDetailsDialogData +} from '@home/components/profile/tenant/rate-limits/rate-limits-details-dialog.component'; +import { + RateLimits, + rateLimitsDialogTitleTranslationMap, + rateLimitsLabelTranslationMap, + RateLimitsType, + stringToRateLimitsArray +} from './rate-limits.models'; +import { isDefined } from '@core/utils'; + +@Component({ + selector: 'tb-rate-limits', + templateUrl: './rate-limits.component.html', + styleUrls: ['./rate-limits.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => RateLimitsComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => RateLimitsComponent), + multi: true, + } + ] +}) +export class RateLimitsComponent implements ControlValueAccessor, OnInit, Validator { + + @Input() + disabled: boolean; + + @Input() + type: RateLimitsType; + + label: string; + + rateLimitsFormGroup: FormGroup; + + get rateLimitsArray(): Array { + return this.rateLimitsFormGroup.get('rateLimits').value; + } + + private modelValue: string; + + private propagateChange = null; + + constructor(private dialog: MatDialog, + private fb: FormBuilder) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.label = rateLimitsLabelTranslationMap.get(this.type); + this.rateLimitsFormGroup = this.fb.group({ + rateLimits: [null, []] + }); + } + + setDisabledState(isDisabled: boolean) { + this.disabled = isDisabled; + if (this.disabled) { + this.rateLimitsFormGroup.disable({emitEvent: false}); + } else { + this.rateLimitsFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: string) { + this.modelValue = value; + this.updateRateLimitsInfo(); + } + + public validate(c: FormControl) { + return null; + } + + public onClick($event: Event) { + if ($event) { + $event.stopPropagation(); + } + const title = rateLimitsDialogTitleTranslationMap.get(this.type); + this.dialog.open(RateLimitsDetailsDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + rateLimits: this.modelValue, + title, + readonly: this.disabled + } + }).afterClosed().subscribe((result) => { + if (isDefined(result)) { + this.modelValue = result; + this.updateModel(); + } + }); + } + + private updateRateLimitsInfo() { + this.rateLimitsFormGroup.patchValue( + { + rateLimits: stringToRateLimitsArray(this.modelValue) + } + ); + } + + private updateModel() { + this.updateRateLimitsInfo(); + this.propagateChange(this.modelValue); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts new file mode 100644 index 0000000000..f137635c33 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits.models.ts @@ -0,0 +1,125 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { TranslateService } from '@ngx-translate/core'; + +export interface RateLimits { + value: string; + time: string; +} + +export enum RateLimitsType { + DEVICE_MESSAGES = 'DEVICE_MESSAGES', + DEVICE_TELEMETRY_MESSAGES = 'DEVICE_TELEMETRY_MESSAGES', + DEVICE_TELEMETRY_DATA_POINTS = 'DEVICE_TELEMETRY_DATA_POINTS', + TENANT_MESSAGES = 'TENANT_MESSAGES', + TENANT_TELEMETRY_MESSAGES = 'TENANT_TELEMETRY_MESSAGES', + TENANT_TELEMETRY_DATA_POINTS = 'TENANT_TELEMETRY_DATA_POINTS', + TENANT_SERVER_REST_LIMITS_CONFIGURATION = 'TENANT_SERVER_REST_LIMITS_CONFIGURATION', + CUSTOMER_SERVER_REST_LIMITS_CONFIGURATION = 'CUSTOMER_SERVER_REST_LIMITS_CONFIGURATION', + WS_UPDATE_PER_SESSION_RATE_LIMIT = 'WS_UPDATE_PER_SESSION_RATE_LIMIT', + CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION = 'CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION', + TENANT_ENTITY_EXPORT_RATE_LIMIT = 'TENANT_ENTITY_EXPORT_RATE_LIMIT', + TENANT_ENTITY_IMPORT_RATE_LIMIT = 'TENANT_ENTITY_IMPORT_RATE_LIMIT' +} + +export const rateLimitsLabelTranslationMap = new Map( + [ + [RateLimitsType.TENANT_MESSAGES, 'tenant-profile.rate-limits.transport-tenant-msg'], + [RateLimitsType.TENANT_TELEMETRY_MESSAGES, 'tenant-profile.rate-limits.transport-tenant-telemetry-msg'], + [RateLimitsType.TENANT_TELEMETRY_DATA_POINTS, 'tenant-profile.rate-limits.transport-tenant-telemetry-data-points'], + [RateLimitsType.DEVICE_MESSAGES, 'tenant-profile.rate-limits.transport-device-msg'], + [RateLimitsType.DEVICE_TELEMETRY_MESSAGES, 'tenant-profile.rate-limits.transport-device-telemetry-msg'], + [RateLimitsType.DEVICE_TELEMETRY_DATA_POINTS, 'tenant-profile.rate-limits.transport-device-telemetry-data-points'], + [RateLimitsType.TENANT_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.transport-tenant-msg-rate-limit'], + [RateLimitsType.CUSTOMER_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.customer-rest-limits'], + [RateLimitsType.WS_UPDATE_PER_SESSION_RATE_LIMIT, 'tenant-profile.ws-limit-updates-per-session'], + [RateLimitsType.CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION, 'tenant-profile.cassandra-tenant-limits-configuration'], + [RateLimitsType.TENANT_ENTITY_EXPORT_RATE_LIMIT, 'tenant-profile.tenant-entity-export-rate-limit'], + [RateLimitsType.TENANT_ENTITY_IMPORT_RATE_LIMIT, 'tenant-profile.tenant-entity-import-rate-limit'], + ] +); + +export const rateLimitsDialogTitleTranslationMap = new Map( + [ + [RateLimitsType.TENANT_MESSAGES, 'tenant-profile.rate-limits.edit-transport-tenant-msg-title'], + [RateLimitsType.TENANT_TELEMETRY_MESSAGES, 'tenant-profile.rate-limits.edit-transport-tenant-telemetry-msg-title'], + [RateLimitsType.TENANT_TELEMETRY_DATA_POINTS, 'tenant-profile.rate-limits.edit-transport-tenant-telemetry-data-points-title'], + [RateLimitsType.DEVICE_MESSAGES, 'tenant-profile.rate-limits.edit-transport-device-msg-title'], + [RateLimitsType.DEVICE_TELEMETRY_MESSAGES, 'tenant-profile.rate-limits.edit-transport-device-telemetry-msg-title'], + [RateLimitsType.DEVICE_TELEMETRY_DATA_POINTS, 'tenant-profile.rate-limits.edit-transport-device-telemetry-data-points-title'], + [RateLimitsType.TENANT_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.rate-limits.edit-transport-tenant-msg-rate-limit-title'], + [RateLimitsType.CUSTOMER_SERVER_REST_LIMITS_CONFIGURATION, 'tenant-profile.rate-limits.edit-customer-rest-limits-title'], + [RateLimitsType.WS_UPDATE_PER_SESSION_RATE_LIMIT, 'tenant-profile.rate-limits.edit-ws-limit-updates-per-session-title'], + [RateLimitsType.CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION, 'tenant-profile.rate-limits.edit-cassandra-tenant-limits-configuration-title'], + [RateLimitsType.TENANT_ENTITY_EXPORT_RATE_LIMIT, 'tenant-profile.rate-limits.edit-tenant-entity-export-rate-limit-title'], + [RateLimitsType.TENANT_ENTITY_IMPORT_RATE_LIMIT, 'tenant-profile.rate-limits.edit-tenant-entity-import-rate-limit-title'], + ] +); + +export function stringToRateLimitsArray(rateLimits: string): Array { + const result: Array = []; + if (rateLimits?.length > 0) { + const rateLimitsArrays = rateLimits.split(','); + for (let i = 0; i < rateLimitsArrays.length; i++) { + const [value, time] = rateLimitsArrays[i].split(':'); + const rateLimitControl = { + value, + time + }; + result.push(rateLimitControl); + } + } + return result; +} + +export function rateLimitsArrayToString(rateLimits: Array): string { + let result = ''; + for (let i = 0; i < rateLimits.length; i++) { + result = result.concat(rateLimits[i].value, ':', rateLimits[i].time); + if ((rateLimits.length > 1) && (i !== rateLimits.length - 1)) { + result = result.concat(','); + } + } + return result; +} + +export function rateLimitsArrayToHtml(translate: TranslateService, rateLimitsArray: Array): string { + const rateLimitsHtml = rateLimitsArray.map((rateLimits, index) => { + const isLast: boolean = index === rateLimitsArray.length - 1; + return rateLimitsToHtml(translate, rateLimits, isLast); + }); + let result: string; + if (rateLimitsHtml.length > 1) { + const butLessThanText = translate.instant('tenant-profile.rate-limits.but-less-than'); + result = rateLimitsHtml.join(' ' + butLessThanText + ' '); + } else { + result = rateLimitsHtml[0]; + } + return result; +} + +function rateLimitsToHtml(translate: TranslateService, rateLimit: RateLimits, isLast: boolean): string { + const value = rateLimit.value; + const time = rateLimit.time; + const operation = translate.instant('tenant-profile.rate-limits.messages-per'); + const seconds = translate.instant('tenant-profile.rate-limits.sec'); + const comma = isLast ? '' : ','; + return `${value} + ${operation} + ${time} + ${seconds}${comma}
`; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html index 4f85de08b0..2327d708da 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.html @@ -36,6 +36,7 @@ [dashboard]="data.dashboard" [aliasController]="data.aliasController" [widget]="data.widget" + [widgetType]="data.widgetType" [showPostProcessing]="data.showPostProcessing" [callbacks]="data.callbacks" formControlName="dataKey"> diff --git a/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts index 5c67242958..a3d5d8fd65 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/data-key-config-dialog.component.ts @@ -22,7 +22,7 @@ import { AppState } from '@core/core.state'; import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { DialogComponent } from '@shared/components/dialog.component'; -import { DataKey, Widget } from '@shared/models/widget.models'; +import { DataKey, Widget, widgetType } from '@shared/models/widget.models'; import { DataKeysCallbacks } from './data-keys.component.models'; import { DataKeyConfigComponent } from '@home/components/widget/data-key-config.component'; import { Dashboard } from '@shared/models/dashboard.models'; @@ -35,6 +35,7 @@ export interface DataKeyConfigDialogData { dashboard: Dashboard; aliasController: IAliasController; widget: Widget; + widgetType: widgetType; entityAliasId?: string; showPostProcessing?: boolean; callbacks?: DataKeysCallbacks; diff --git a/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.html b/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.html index 19cc902098..9088f5cc33 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.html @@ -62,6 +62,75 @@ +
+ + datakey.aggregation + + + {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} + + + + {{ dataKeyFormGroup.get('aggregationType').value ? (dataKeyAggregationTypeHintTranslations.get(aggregationTypes[dataKeyFormGroup.get('aggregationType').value]) | translate) : '' }} +
+ {{ 'datakey.aggregation-type-hint-common' | translate }} +
+
+
+
+ datakey.delta-calculation + + + + + {{ 'datakey.enable-delta-calculation' | translate }} + + {{ 'datakey.enable-delta-calculation-hint' | translate }} + + + +
+ + widgets.chart.time-for-comparison + + + {{ 'widgets.chart.time-for-comparison-previous-interval' | translate }} + + + {{ 'widgets.chart.time-for-comparison-days' | translate }} + + + {{ 'widgets.chart.time-for-comparison-weeks' | translate }} + + + {{ 'widgets.chart.time-for-comparison-months' | translate }} + + + {{ 'widgets.chart.time-for-comparison-years' | translate }} + + + {{ 'widgets.chart.time-for-comparison-custom-interval' | translate }} + + + + + widgets.chart.custom-interval-value + + + + datakey.delta-calculation-result + + + {{ comparisonResultTypeTranslations.get(comparisonResultTypes[comparisonResultType]) | translate }} + + + +
+
+
+
+
datakey.data-generation-func
diff --git a/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.scss index 8b71ee3664..7fd1fba872 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.scss @@ -28,6 +28,36 @@ padding-left: 12px; } } + + .fields-group { + padding: 0 16px 8px; + margin-bottom: 10px; + border: 1px groove rgba(0, 0, 0, .25); + border-radius: 4px; + + legend { + color: rgba(0, 0, 0, .7); + width: fit-content; + } + + legend + * { + display: block; + margin-top: 16px; + } + + &.fields-group-slider { + padding: 0; + + legend { + margin-left: 16px; + } + + > .tb-settings { + margin-top: 0; + padding: 0 16px 8px; + } + } + } } } @@ -42,5 +72,50 @@ } } } + .mat-expansion-panel { + &.tb-settings { + box-shadow: none; + + .mat-content { + overflow: visible; + } + + .mat-expansion-panel-header { + padding: 0; + color: rgba(0, 0, 0, 0.87); + height: 140px; + + &:hover { + background: none; + } + + .mat-expansion-indicator { + padding: 2px; + } + } + + .mat-expansion-panel-header-description { + align-items: center; + } + + > .mat-expansion-panel-content { + > .mat-expansion-panel-body { + padding: 0; + } + } + } + + .mat-expansion-panel-content { + font: inherit; + } + } + + .mat-slide { + margin: 8px 0; + } + + .mat-slide-toggle-content { + white-space: normal; + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts index b97f17f0e0..a461796111 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.ts @@ -18,7 +18,13 @@ import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@an import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { DataKey, Widget } from '@shared/models/widget.models'; +import { + ComparisonResultType, comparisonResultTypeTranslationMap, + DataKey, + dataKeyAggregationTypeHintTranslationMap, + Widget, + widgetType +} from '@shared/models/widget.models'; import { ControlValueAccessor, FormBuilder, @@ -43,6 +49,7 @@ import { JsonFormComponentData } from '@shared/components/json-form/json-form-co import { WidgetService } from '@core/http/widget.service'; import { Dashboard } from '@shared/models/dashboard.models'; import { IAliasController } from '@core/api/widget-api.models'; +import { aggregationTranslations, AggregationType, ComparisonDuration } from '@shared/models/time/time.models'; @Component({ selector: 'tb-data-key-config', @@ -65,6 +72,22 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con dataKeyTypes = DataKeyType; + widgetTypes = widgetType; + + aggregations = [AggregationType.NONE, ...Object.keys(AggregationType).filter(type => type !== AggregationType.NONE)]; + + aggregationTypes = AggregationType; + + aggregationTypesTranslations = aggregationTranslations; + + dataKeyAggregationTypeHintTranslations = dataKeyAggregationTypeHintTranslationMap; + + comparisonResultTypes = ComparisonResultType; + + comparisonResults = Object.keys(ComparisonResultType); + + comparisonResultTypeTranslations = comparisonResultTypeTranslationMap; + @Input() entityAliasId: string; @@ -80,6 +103,9 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con @Input() widget: Widget; + @Input() + widgetType: widgetType; + @Input() dataKeySettingsSchema: any; @@ -155,6 +181,11 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con } this.dataKeyFormGroup = this.fb.group({ name: [null, []], + aggregationType: [null, []], + comparisonEnabled: [null, []], + timeForComparison: [null, [Validators.required]], + comparisonCustomIntervalValue: [null, [Validators.required, Validators.min(1000)]], + comparisonResultType: [null, [Validators.required]], label: [null, [Validators.required]], color: [null, [Validators.required]], units: [null, []], @@ -164,6 +195,32 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con postFuncBody: [null, []] }); + this.dataKeyFormGroup.get('aggregationType').valueChanges.subscribe( + (aggType) => { + if (!this.dataKeyFormGroup.get('label').dirty) { + let newLabel = this.dataKeyFormGroup.get('name').value; + if (aggType !== AggregationType.NONE) { + const prefix = this.translate.instant(aggregationTranslations.get(aggType)); + newLabel = prefix + ' ' + newLabel; + } + this.dataKeyFormGroup.get('label').patchValue(newLabel); + } + this.updateComparisonValidators(); + } + ); + + this.dataKeyFormGroup.get('comparisonEnabled').valueChanges.subscribe( + () => { + this.updateComparisonValues(); + } + ); + + this.dataKeyFormGroup.get('timeForComparison').valueChanges.subscribe( + () => { + this.updateComparisonValues(); + } + ); + this.dataKeyFormGroup.valueChanges.subscribe(() => { this.updateModel(); }); @@ -199,22 +256,86 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con if (this.modelValue.postFuncBody && this.modelValue.postFuncBody.length) { this.modelValue.usePostProcessing = true; } + if (this.widgetType === widgetType.latest && this.modelValue.type === DataKeyType.timeseries && !this.modelValue.aggregationType) { + this.modelValue.aggregationType = AggregationType.NONE; + } this.dataKeyFormGroup.patchValue(this.modelValue, {emitEvent: false}); + this.updateValidators(); + if (this.displayAdvanced) { + this.dataKeySettingsData.model = this.modelValue.settings; + this.dataKeySettingsFormGroup.patchValue({ + settings: this.dataKeySettingsData + }, {emitEvent: false}); + } + } + + private updateValidators() { this.dataKeyFormGroup.get('name').setValidators(this.modelValue.type !== DataKeyType.function && - this.modelValue.type !== DataKeyType.count - ? [Validators.required] : []); + this.modelValue.type !== DataKeyType.count + ? [Validators.required] : []); if (this.modelValue.type === DataKeyType.count) { this.dataKeyFormGroup.get('name').disable({emitEvent: false}); } else { this.dataKeyFormGroup.get('name').enable({emitEvent: false}); } this.dataKeyFormGroup.get('name').updateValueAndValidity({emitEvent: false}); - if (this.displayAdvanced) { - this.dataKeySettingsData.model = this.modelValue.settings; - this.dataKeySettingsFormGroup.patchValue({ - settings: this.dataKeySettingsData - }, {emitEvent: false}); + this.updateComparisonValidators(); + } + + private updateComparisonValues() { + const comparisonEnabled = this.dataKeyFormGroup.get('comparisonEnabled').value; + if (comparisonEnabled) { + const timeForComparison: ComparisonDuration = this.dataKeyFormGroup.get('timeForComparison').value; + if (!timeForComparison) { + this.dataKeyFormGroup.get('timeForComparison').patchValue('previousInterval', {emitEvent: false}); + } else if (timeForComparison === 'customInterval') { + const comparisonCustomIntervalValue = this.dataKeyFormGroup.get('comparisonCustomIntervalValue').value; + if (!comparisonCustomIntervalValue) { + this.dataKeyFormGroup.get('comparisonCustomIntervalValue').patchValue(7200000, {emitEvent: false}); + } + } + const comparisonResultType: ComparisonResultType = this.dataKeyFormGroup.get('comparisonResultType').value; + if (!comparisonResultType) { + this.dataKeyFormGroup.get('comparisonResultType').patchValue(ComparisonResultType.DELTA_ABSOLUTE, {emitEvent: false}); + } + } + this.updateComparisonValidators(); + } + + private updateComparisonValidators() { + const aggregationType: AggregationType = this.dataKeyFormGroup.get('aggregationType').value; + if (aggregationType && aggregationType !== AggregationType.NONE) { + this.dataKeyFormGroup.get('comparisonEnabled').enable({emitEvent: false}); + const comparisonEnabled = this.dataKeyFormGroup.get('comparisonEnabled').value; + if (comparisonEnabled) { + this.dataKeyFormGroup.get('timeForComparison').enable({emitEvent: false}); + const timeForComparison: ComparisonDuration = this.dataKeyFormGroup.get('timeForComparison').value; + if (timeForComparison) { + this.dataKeyFormGroup.get('comparisonResultType').enable({emitEvent: false}); + if (timeForComparison === 'customInterval') { + this.dataKeyFormGroup.get('comparisonCustomIntervalValue').enable({emitEvent: false}); + } else { + this.dataKeyFormGroup.get('comparisonCustomIntervalValue').disable({emitEvent: false}); + } + } else { + this.dataKeyFormGroup.get('comparisonResultType').disable({emitEvent: false}); + this.dataKeyFormGroup.get('comparisonCustomIntervalValue').disable({emitEvent: false}); + } + } else { + this.dataKeyFormGroup.get('timeForComparison').disable({emitEvent: false}); + this.dataKeyFormGroup.get('comparisonResultType').disable({emitEvent: false}); + this.dataKeyFormGroup.get('comparisonCustomIntervalValue').disable({emitEvent: false}); + } + } else { + this.dataKeyFormGroup.get('comparisonEnabled').disable({emitEvent: false}); + this.dataKeyFormGroup.get('timeForComparison').disable({emitEvent: false}); + this.dataKeyFormGroup.get('comparisonResultType').disable({emitEvent: false}); + this.dataKeyFormGroup.get('comparisonCustomIntervalValue').disable({emitEvent: false}); } + this.dataKeyFormGroup.get('comparisonEnabled').updateValueAndValidity({emitEvent: false}); + this.dataKeyFormGroup.get('timeForComparison').updateValueAndValidity({emitEvent: false}); + this.dataKeyFormGroup.get('comparisonResultType').updateValueAndValidity({emitEvent: false}); + this.dataKeyFormGroup.get('comparisonCustomIntervalValue').updateValueAndValidity({emitEvent: false}); } private updateModel() { diff --git a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.html b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.html index 3527a5ca2e..5df377cf07 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.html @@ -58,12 +58,7 @@ {{key.label}}
:
-
- f({{key.name}}) - - {{key.name}} - -
+