Browse Source
# Conflicts: # application/src/main/data/upgrade/3.4.3/schema_update.sql # application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java # application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java # application/src/main/resources/thingsboard.yml # dao/src/main/resources/sql/schema-entities-idx.sql # dao/src/main/resources/sql/schema-entities.sql # pom.xmlpull/8046/head
273 changed files with 10027 additions and 1145 deletions
@ -0,0 +1,107 @@ |
|||
## Install ThingsBoard Edge and connect to cloud instructions |
|||
|
|||
Here is the list of commands, that can be used to quickly install and connect ThingsBoard Edge to the cloud using docker compose. |
|||
|
|||
### Prerequisites |
|||
|
|||
Install <a href="https://docs.docker.com/engine/install/" target="_blank"> Docker CE</a> and <a href="https://docs.docker.com/compose/install/" target="_blank"> Docker Compose</a>. |
|||
|
|||
### Create data and logs folders |
|||
|
|||
Run following commands, before starting docker container(s), to create folders for storing data and logs. |
|||
These commands additionally will change owner of newly created folders to docker container user. |
|||
To do this (to change user) **chown** command is used, and this command requires *sudo* permissions (command will request password for a *sudo* access): |
|||
|
|||
```bash |
|||
mkdir -p ~/.mytb-edge-data && sudo chown -R 799:799 ~/.mytb-edge-data |
|||
mkdir -p ~/.mytb-edge-logs && sudo chown -R 799:799 ~/.mytb-edge-logs |
|||
{:copy-code} |
|||
``` |
|||
|
|||
### Running ThingsBoard Edge as docker service |
|||
|
|||
${LOCALHOST_WARNING} |
|||
|
|||
Create docker compose file for ThingsBoard Edge service: |
|||
|
|||
```bash |
|||
nano docker-compose.yml |
|||
{:copy-code} |
|||
``` |
|||
|
|||
Add the following lines to the yml file: |
|||
|
|||
```bash |
|||
version: '3.0' |
|||
services: |
|||
mytbedge: |
|||
restart: always |
|||
image: "thingsboard/tb-edge:${TB_EDGE_VERSION}" |
|||
ports: |
|||
- "8080:8080" |
|||
- "1883:1883" |
|||
- "5683-5688:5683-5688/udp" |
|||
environment: |
|||
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/tb-edge |
|||
CLOUD_ROUTING_KEY: ${CLOUD_ROUTING_KEY} |
|||
CLOUD_ROUTING_SECRET: ${CLOUD_ROUTING_SECRET} |
|||
CLOUD_RPC_HOST: ${BASE_URL} |
|||
CLOUD_RPC_PORT: ${CLOUD_RPC_PORT} |
|||
CLOUD_RPC_SSL_ENABLED: ${CLOUD_RPC_SSL_ENABLED} |
|||
volumes: |
|||
- ~/.mytb-edge-data:/data |
|||
- ~/.mytb-edge-logs:/var/log/tb-edge |
|||
postgres: |
|||
restart: always |
|||
image: "postgres:12" |
|||
ports: |
|||
- "5432" |
|||
environment: |
|||
POSTGRES_DB: tb-edge |
|||
POSTGRES_PASSWORD: postgres |
|||
volumes: |
|||
- ~/.mytb-edge-data/db:/var/lib/postgresql/data |
|||
{:copy-code} |
|||
``` |
|||
|
|||
#### [Optional] Update bind ports |
|||
If ThingsBoard Edge is going to be running on the same machine where ThingsBoard server (cloud) is running, you'll need to update docker compose port mapping to avoid port collision between ThingsBoard server and ThingsBoard Edge. |
|||
|
|||
Please update next lines of `docker-compose.yml` file: |
|||
|
|||
```bash |
|||
ports: |
|||
- "18080:8080" |
|||
- "11883:1883" |
|||
- "15683-15688:5683-5688/udp" |
|||
``` |
|||
Make sure that ports above (18080, 11883, 15683-15688) are not used by any other application. |
|||
|
|||
#### Start ThingsBoard Edge |
|||
Set the terminal in the directory which contains the `docker-compose.yml` file and execute the following commands to up this docker compose directly: |
|||
|
|||
```bash |
|||
docker compose up -d |
|||
docker compose logs -f mytbedge |
|||
{:copy-code} |
|||
``` |
|||
|
|||
###### NOTE: Docker Compose V2 vs docker-compose (with a hyphen) |
|||
|
|||
ThingsBoard supports Docker Compose V2 (Docker Desktop or Compose plugin) starting from **3.4.2** release, because **docker-compose** as standalone setup is no longer supported by Docker. |
|||
We **strongly** recommend to update to Docker Compose V2 and use it. |
|||
If you still rely on using Docker Compose as docker-compose (with a hyphen), then please execute the following commands to start ThingsBoard Edge: |
|||
|
|||
```bash |
|||
docker-compose up -d |
|||
docker-compose logs -f mytbedge |
|||
``` |
|||
|
|||
#### Open ThingsBoard Edge UI |
|||
|
|||
Once started, you will be able to open **ThingsBoard Edge UI** using the following link http://localhost:8080. |
|||
|
|||
###### NOTE: Edge HTTP bind port update |
|||
|
|||
Use next **ThingsBoard Edge UI** link **http://localhost:18080** if you updated HTTP 8080 bind port to **18080**. |
|||
|
|||
@ -0,0 +1,3 @@ |
|||
###### WARNING NOTE: 'localhost' can not be used as CLOUD_RPC_HOST |
|||
|
|||
Please note that your ThingsBoard base URL is **'localhost'** at the moment. **'localhost'** cannot be used for docker containers - please update **CLOUD_RPC_HOST** environment variable below to the IP address of your machine (*docker **host** machine*). IP address must be `192.168.1.XX` or similar format. In other case - ThingsBoard Edge service, that is running in docker container, will not be able to connect to the cloud. |
|||
@ -0,0 +1,127 @@ |
|||
/** |
|||
* 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.controller; |
|||
|
|||
import io.swagger.annotations.ApiOperation; |
|||
import io.swagger.annotations.ApiParam; |
|||
import lombok.RequiredArgsConstructor; |
|||
import org.springframework.http.MediaType; |
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import org.springframework.web.bind.annotation.PathVariable; |
|||
import org.springframework.web.bind.annotation.RequestBody; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
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.alarm.Alarm; |
|||
import org.thingsboard.server.common.data.alarm.AlarmComment; |
|||
import org.thingsboard.server.common.data.alarm.AlarmCommentInfo; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.AlarmCommentId; |
|||
import org.thingsboard.server.common.data.id.AlarmId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.entitiy.alarm.TbAlarmCommentService; |
|||
import org.thingsboard.server.service.security.permission.Operation; |
|||
|
|||
import static org.thingsboard.server.controller.ControllerConstants.ALARM_COMMENT_ID_PARAM_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.ALARM_COMMENT_SORT_PROPERTY_ALLOWABLE_VALUES; |
|||
import static org.thingsboard.server.controller.ControllerConstants.ALARM_ID_PARAM_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; |
|||
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; |
|||
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; |
|||
import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; |
|||
import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; |
|||
|
|||
@RestController |
|||
@TbCoreComponent |
|||
@RequiredArgsConstructor |
|||
@RequestMapping("/api") |
|||
public class AlarmCommentController extends BaseController { |
|||
public static final String ALARM_ID = "alarmId"; |
|||
public static final String ALARM_COMMENT_ID = "commentId"; |
|||
|
|||
private final TbAlarmCommentService tbAlarmCommentService; |
|||
|
|||
@ApiOperation(value = "Create or update Alarm Comment ", |
|||
notes = "Creates or Updates the Alarm Comment. " + |
|||
"When creating comment, platform generates Alarm Comment Id as " + UUID_WIKI_LINK + |
|||
"The newly created Alarm Comment id will be present in the response. Specify existing Alarm Comment id to update the alarm. " + |
|||
"Referencing non-existing Alarm Comment Id will cause 'Not Found' error. " + |
|||
"\n\n To create new Alarm comment entity it is enough to specify 'comment' json element with 'text' node, for example: {\"comment\": { \"text\": \"my comment\"}}. " + |
|||
"\n\n If comment type is not specified the default value 'OTHER' will be saved. If 'alarmId' or 'userId' specified in body it will be ignored." + |
|||
TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH |
|||
, produces = MediaType.APPLICATION_JSON_VALUE) |
|||
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
|||
@RequestMapping(value = "/alarm/{alarmId}/comment", method = RequestMethod.POST) |
|||
@ResponseBody |
|||
public AlarmComment saveAlarmComment(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) |
|||
@PathVariable(ALARM_ID) String strAlarmId, @ApiParam(value = "A JSON value representing the comment.") @RequestBody AlarmComment alarmComment) throws ThingsboardException { |
|||
checkParameter(ALARM_ID, strAlarmId); |
|||
AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); |
|||
Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); |
|||
alarmComment.setAlarmId(alarmId); |
|||
return tbAlarmCommentService.saveAlarmComment(alarm, alarmComment, getCurrentUser()); |
|||
} |
|||
|
|||
@ApiOperation(value = "Delete Alarm comment (deleteAlarmComment)", |
|||
notes = "Deletes the Alarm comment. Referencing non-existing Alarm comment Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) |
|||
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
|||
@RequestMapping(value = "/alarm/{alarmId}/comment/{commentId}", method = RequestMethod.DELETE) |
|||
@ResponseBody |
|||
public void deleteAlarmComment(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId, @ApiParam(value = ALARM_COMMENT_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_COMMENT_ID) String strCommentId) throws ThingsboardException { |
|||
checkParameter(ALARM_ID, strAlarmId); |
|||
AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); |
|||
Alarm alarm = checkAlarmId(alarmId, Operation.DELETE); |
|||
|
|||
AlarmCommentId alarmCommentId = new AlarmCommentId(toUUID(strCommentId)); |
|||
AlarmComment alarmComment = checkAlarmCommentId(alarmCommentId, alarmId); |
|||
tbAlarmCommentService.deleteAlarmComment(alarm, alarmComment, getCurrentUser()); |
|||
} |
|||
|
|||
@ApiOperation(value = "Get Alarm comments (getAlarmComments)", |
|||
notes = "Returns a page of alarm comments for specified alarm. " + |
|||
PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) |
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") |
|||
@RequestMapping(value = "/alarm/{alarmId}/comment", method = RequestMethod.GET) |
|||
@ResponseBody |
|||
public PageData<AlarmCommentInfo> getAlarmComments( |
|||
@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) |
|||
@PathVariable(ALARM_ID) String strAlarmId, |
|||
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) |
|||
@RequestParam int pageSize, |
|||
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) |
|||
@RequestParam int page, |
|||
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ALARM_COMMENT_SORT_PROPERTY_ALLOWABLE_VALUES) |
|||
@RequestParam(required = false) String sortProperty, |
|||
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) |
|||
@RequestParam(required = false) String sortOrder |
|||
) throws Exception { |
|||
checkParameter(ALARM_ID, strAlarmId); |
|||
AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); |
|||
Alarm alarm = alarmService.findAlarmByIdAsync(getCurrentUser().getTenantId(), alarmId).get(); |
|||
checkNotNull(alarm, "Alarm with id [" + alarmId + "] is not found"); |
|||
checkEntityId(alarm.getOriginator(), Operation.READ); |
|||
|
|||
PageLink pageLink = createPageLink(pageSize, page, null, sortProperty, sortOrder); |
|||
return checkNotNull(alarmCommentService.findAlarmComments(alarm.getTenantId(), alarmId, pageLink)); |
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
/** |
|||
* 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.edge.instructions; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.edge.Edge; |
|||
import org.thingsboard.server.common.data.edge.EdgeInstallInstructions; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.install.InstallScripts; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
import java.io.IOException; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.nio.file.Files; |
|||
import java.nio.file.Path; |
|||
import java.nio.file.Paths; |
|||
|
|||
@Service |
|||
@Slf4j |
|||
@RequiredArgsConstructor |
|||
@ConditionalOnProperty(prefix = "edges", value = "enabled", havingValue = "true") |
|||
@TbCoreComponent |
|||
public class DefaultEdgeInstallService implements EdgeInstallService { |
|||
|
|||
private static final String EDGE_DIR = "edge"; |
|||
|
|||
private static final String EDGE_INSTALL_INSTRUCTIONS_DIR = "install_instructions"; |
|||
|
|||
private final InstallScripts installScripts; |
|||
|
|||
@Value("${edges.rpc.port}") |
|||
private int rpcPort; |
|||
|
|||
@Value("${edges.rpc.ssl.enabled}") |
|||
private boolean sslEnabled; |
|||
|
|||
@Value("${app.version:unknown}") |
|||
private String appVersion; |
|||
|
|||
@Override |
|||
public EdgeInstallInstructions getDockerInstallInstructions(TenantId tenantId, Edge edge, HttpServletRequest request) { |
|||
String dockerInstallInstructions = readFile(resolveFile("docker", "instructions.md")); |
|||
String baseUrl = request.getServerName(); |
|||
if (baseUrl.contains("localhost") || baseUrl.contains("127.0.0.1")) { |
|||
String localhostWarning = readFile(resolveFile("docker", "localhost_warning.md")); |
|||
dockerInstallInstructions = dockerInstallInstructions.replace("${LOCALHOST_WARNING}", localhostWarning); |
|||
dockerInstallInstructions = dockerInstallInstructions.replace("${BASE_URL}", "!!!REPLACE_ME_TO_HOST_IP_ADDRESS!!!"); |
|||
} else { |
|||
dockerInstallInstructions = dockerInstallInstructions.replace("${LOCALHOST_WARNING}", ""); |
|||
dockerInstallInstructions = dockerInstallInstructions.replace("${BASE_URL}", baseUrl); |
|||
} |
|||
dockerInstallInstructions = dockerInstallInstructions.replace("${TB_EDGE_VERSION}", appVersion + "EDGE"); |
|||
dockerInstallInstructions = dockerInstallInstructions.replace("${CLOUD_ROUTING_KEY}", edge.getRoutingKey()); |
|||
dockerInstallInstructions = dockerInstallInstructions.replace("${CLOUD_ROUTING_SECRET}", edge.getSecret()); |
|||
dockerInstallInstructions = dockerInstallInstructions.replace("${CLOUD_RPC_PORT}", Integer.toString(rpcPort)); |
|||
dockerInstallInstructions = dockerInstallInstructions.replace("${CLOUD_RPC_SSL_ENABLED}", Boolean.toString(sslEnabled)); |
|||
return new EdgeInstallInstructions(dockerInstallInstructions); |
|||
} |
|||
|
|||
private String readFile(Path file) { |
|||
try { |
|||
return new String(Files.readAllBytes(file), StandardCharsets.UTF_8); |
|||
} catch (IOException e) { |
|||
log.warn("Failed to read file: {}", file, e); |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
private Path resolveFile(String subDir, String... subDirs) { |
|||
return getEdgeInstallInstructionsDir().resolve(Paths.get(subDir, subDirs)); |
|||
} |
|||
|
|||
private Path getEdgeInstallInstructionsDir() { |
|||
return Paths.get(installScripts.getDataDir(), InstallScripts.JSON_DIR, EDGE_DIR, EDGE_INSTALL_INSTRUCTIONS_DIR); |
|||
} |
|||
} |
|||
@ -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.service.edge.instructions; |
|||
|
|||
import org.thingsboard.server.common.data.edge.Edge; |
|||
import org.thingsboard.server.common.data.edge.EdgeInstallInstructions; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
|
|||
public interface EdgeInstallService { |
|||
|
|||
EdgeInstallInstructions getDockerInstallInstructions(TenantId tenantId, Edge edge, HttpServletRequest request); |
|||
|
|||
} |
|||
@ -1,195 +0,0 @@ |
|||
/** |
|||
* 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.edge.rpc.processor; |
|||
|
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Component; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.EdgeUtils; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.alarm.Alarm; |
|||
import org.thingsboard.server.common.data.alarm.AlarmSeverity; |
|||
import org.thingsboard.server.common.data.alarm.AlarmStatus; |
|||
import org.thingsboard.server.common.data.edge.EdgeEvent; |
|||
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
|||
import org.thingsboard.server.common.data.edge.EdgeEventType; |
|||
import org.thingsboard.server.common.data.id.AlarmId; |
|||
import org.thingsboard.server.common.data.id.EdgeId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
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.gen.edge.v1.AlarmUpdateMsg; |
|||
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; |
|||
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
@Component |
|||
@Slf4j |
|||
@TbCoreComponent |
|||
public class AlarmEdgeProcessor extends BaseEdgeProcessor { |
|||
|
|||
public ListenableFuture<Void> processAlarmFromEdge(TenantId tenantId, AlarmUpdateMsg alarmUpdateMsg) { |
|||
log.trace("[{}] onAlarmUpdate [{}]", tenantId, alarmUpdateMsg); |
|||
EntityId originatorId = getAlarmOriginator(tenantId, alarmUpdateMsg.getOriginatorName(), |
|||
EntityType.valueOf(alarmUpdateMsg.getOriginatorType())); |
|||
if (originatorId == null) { |
|||
log.warn("Originator not found for the alarm msg {}", alarmUpdateMsg); |
|||
return Futures.immediateFuture(null); |
|||
} |
|||
try { |
|||
Alarm existentAlarm = alarmService.findLatestByOriginatorAndType(tenantId, originatorId, alarmUpdateMsg.getType()).get(); |
|||
switch (alarmUpdateMsg.getMsgType()) { |
|||
case ENTITY_CREATED_RPC_MESSAGE: |
|||
case ENTITY_UPDATED_RPC_MESSAGE: |
|||
if (existentAlarm == null || existentAlarm.getStatus().isCleared()) { |
|||
existentAlarm = new Alarm(); |
|||
existentAlarm.setTenantId(tenantId); |
|||
existentAlarm.setType(alarmUpdateMsg.getName()); |
|||
existentAlarm.setOriginator(originatorId); |
|||
existentAlarm.setSeverity(AlarmSeverity.valueOf(alarmUpdateMsg.getSeverity())); |
|||
existentAlarm.setStartTs(alarmUpdateMsg.getStartTs()); |
|||
existentAlarm.setClearTs(alarmUpdateMsg.getClearTs()); |
|||
existentAlarm.setPropagate(alarmUpdateMsg.getPropagate()); |
|||
} |
|||
existentAlarm.setStatus(AlarmStatus.valueOf(alarmUpdateMsg.getStatus())); |
|||
existentAlarm.setAckTs(alarmUpdateMsg.getAckTs()); |
|||
existentAlarm.setEndTs(alarmUpdateMsg.getEndTs()); |
|||
existentAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails())); |
|||
alarmService.createOrUpdateAlarm(existentAlarm); |
|||
break; |
|||
case ALARM_ACK_RPC_MESSAGE: |
|||
if (existentAlarm != null) { |
|||
alarmService.ackAlarm(tenantId, existentAlarm.getId(), alarmUpdateMsg.getAckTs()); |
|||
} |
|||
break; |
|||
case ALARM_CLEAR_RPC_MESSAGE: |
|||
if (existentAlarm != null) { |
|||
alarmService.clearAlarm(tenantId, existentAlarm.getId(), |
|||
JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails()), alarmUpdateMsg.getAckTs()); |
|||
} |
|||
break; |
|||
case ENTITY_DELETED_RPC_MESSAGE: |
|||
if (existentAlarm != null) { |
|||
alarmService.deleteAlarm(tenantId, existentAlarm.getId()); |
|||
} |
|||
break; |
|||
} |
|||
return Futures.immediateFuture(null); |
|||
} catch (Exception e) { |
|||
log.error("Failed to process alarm update msg [{}]", alarmUpdateMsg, e); |
|||
return Futures.immediateFailedFuture(new RuntimeException("Failed to process alarm update msg", e)); |
|||
} |
|||
} |
|||
|
|||
private EntityId getAlarmOriginator(TenantId tenantId, String entityName, EntityType entityType) { |
|||
switch (entityType) { |
|||
case DEVICE: |
|||
return deviceService.findDeviceByTenantIdAndName(tenantId, entityName).getId(); |
|||
case ASSET: |
|||
return assetService.findAssetByTenantIdAndName(tenantId, entityName).getId(); |
|||
case ENTITY_VIEW: |
|||
return entityViewService.findEntityViewByTenantIdAndName(tenantId, entityName).getId(); |
|||
default: |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public DownlinkMsg convertAlarmEventToDownlink(EdgeEvent edgeEvent) { |
|||
AlarmId alarmId = new AlarmId(edgeEvent.getEntityId()); |
|||
DownlinkMsg downlinkMsg = null; |
|||
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); |
|||
switch (edgeEvent.getAction()) { |
|||
case ADDED: |
|||
case UPDATED: |
|||
case ALARM_ACK: |
|||
case ALARM_CLEAR: |
|||
try { |
|||
Alarm alarm = alarmService.findAlarmByIdAsync(edgeEvent.getTenantId(), alarmId).get(); |
|||
if (alarm != null) { |
|||
downlinkMsg = DownlinkMsg.newBuilder() |
|||
.setDownlinkMsgId(EdgeUtils.nextPositiveInt()) |
|||
.addAlarmUpdateMsg(alarmMsgConstructor.constructAlarmUpdatedMsg(edgeEvent.getTenantId(), msgType, alarm)) |
|||
.build(); |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("Can't process alarm msg [{}] [{}]", edgeEvent, msgType, e); |
|||
} |
|||
break; |
|||
case DELETED: |
|||
Alarm alarm = JacksonUtil.OBJECT_MAPPER.convertValue(edgeEvent.getBody(), Alarm.class); |
|||
AlarmUpdateMsg alarmUpdateMsg = |
|||
alarmMsgConstructor.constructAlarmUpdatedMsg(edgeEvent.getTenantId(), msgType, alarm); |
|||
downlinkMsg = DownlinkMsg.newBuilder() |
|||
.setDownlinkMsgId(EdgeUtils.nextPositiveInt()) |
|||
.addAlarmUpdateMsg(alarmUpdateMsg) |
|||
.build(); |
|||
break; |
|||
} |
|||
return downlinkMsg; |
|||
} |
|||
|
|||
public ListenableFuture<Void> processAlarmNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException { |
|||
EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); |
|||
AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); |
|||
switch (actionType) { |
|||
case DELETED: |
|||
EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB())); |
|||
Alarm deletedAlarm = JacksonUtil.OBJECT_MAPPER.readValue(edgeNotificationMsg.getBody(), Alarm.class); |
|||
return saveEdgeEvent(tenantId, edgeId, EdgeEventType.ALARM, actionType, alarmId, JacksonUtil.OBJECT_MAPPER.valueToTree(deletedAlarm)); |
|||
default: |
|||
ListenableFuture<Alarm> alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId); |
|||
return Futures.transformAsync(alarmFuture, alarm -> { |
|||
if (alarm == null) { |
|||
return Futures.immediateFuture(null); |
|||
} |
|||
EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(alarm.getOriginator().getEntityType()); |
|||
if (type == null) { |
|||
return Futures.immediateFuture(null); |
|||
} |
|||
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); |
|||
PageData<EdgeId> pageData; |
|||
List<ListenableFuture<Void>> futures = new ArrayList<>(); |
|||
do { |
|||
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, alarm.getOriginator(), pageLink); |
|||
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { |
|||
for (EdgeId relatedEdgeId : pageData.getData()) { |
|||
futures.add(saveEdgeEvent(tenantId, |
|||
relatedEdgeId, |
|||
EdgeEventType.ALARM, |
|||
EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()), |
|||
alarmId, |
|||
null)); |
|||
} |
|||
if (pageData.hasNext()) { |
|||
pageLink = pageLink.nextPageLink(); |
|||
} |
|||
} |
|||
} while (pageData != null && pageData.hasNext()); |
|||
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); |
|||
}, dbCallbackExecutorService); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -1,153 +0,0 @@ |
|||
/** |
|||
* 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.edge.rpc.processor; |
|||
|
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Component; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.EdgeUtils; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.edge.EdgeEvent; |
|||
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
|||
import org.thingsboard.server.common.data.edge.EdgeEventType; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.AssetId; |
|||
import org.thingsboard.server.common.data.id.CustomerId; |
|||
import org.thingsboard.server.common.data.id.DashboardId; |
|||
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.EntityIdFactory; |
|||
import org.thingsboard.server.common.data.id.EntityViewId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.id.UserId; |
|||
import org.thingsboard.server.common.data.relation.EntityRelation; |
|||
import org.thingsboard.server.common.data.relation.RelationTypeGroup; |
|||
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; |
|||
import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; |
|||
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.HashSet; |
|||
import java.util.List; |
|||
import java.util.Set; |
|||
import java.util.UUID; |
|||
|
|||
@Component |
|||
@Slf4j |
|||
@TbCoreComponent |
|||
public class RelationEdgeProcessor extends BaseEdgeProcessor { |
|||
|
|||
public ListenableFuture<Void> processRelationFromEdge(TenantId tenantId, RelationUpdateMsg relationUpdateMsg) { |
|||
log.trace("[{}] onRelationUpdate [{}]", tenantId, relationUpdateMsg); |
|||
try { |
|||
EntityRelation entityRelation = new EntityRelation(); |
|||
|
|||
UUID fromUUID = new UUID(relationUpdateMsg.getFromIdMSB(), relationUpdateMsg.getFromIdLSB()); |
|||
EntityId fromId = EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(relationUpdateMsg.getFromEntityType()), fromUUID); |
|||
entityRelation.setFrom(fromId); |
|||
|
|||
UUID toUUID = new UUID(relationUpdateMsg.getToIdMSB(), relationUpdateMsg.getToIdLSB()); |
|||
EntityId toId = EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(relationUpdateMsg.getToEntityType()), toUUID); |
|||
entityRelation.setTo(toId); |
|||
|
|||
entityRelation.setType(relationUpdateMsg.getType()); |
|||
if (relationUpdateMsg.hasTypeGroup()) { |
|||
entityRelation.setTypeGroup(RelationTypeGroup.valueOf(relationUpdateMsg.getTypeGroup())); |
|||
} |
|||
entityRelation.setAdditionalInfo(JacksonUtil.OBJECT_MAPPER.readTree(relationUpdateMsg.getAdditionalInfo())); |
|||
switch (relationUpdateMsg.getMsgType()) { |
|||
case ENTITY_CREATED_RPC_MESSAGE: |
|||
case ENTITY_UPDATED_RPC_MESSAGE: |
|||
if (isEntityExists(tenantId, entityRelation.getTo()) |
|||
&& isEntityExists(tenantId, entityRelation.getFrom())) { |
|||
relationService.saveRelationAsync(tenantId, entityRelation); |
|||
} |
|||
break; |
|||
case ENTITY_DELETED_RPC_MESSAGE: |
|||
relationService.deleteRelation(tenantId, entityRelation); |
|||
break; |
|||
case UNRECOGNIZED: |
|||
log.error("Unsupported msg type"); |
|||
} |
|||
return Futures.immediateFuture(null); |
|||
} catch (Exception e) { |
|||
log.error("Failed to process relation update msg [{}]", relationUpdateMsg, e); |
|||
return Futures.immediateFailedFuture(new RuntimeException("Failed to process relation update msg", e)); |
|||
} |
|||
} |
|||
|
|||
|
|||
private boolean isEntityExists(TenantId tenantId, EntityId entityId) throws ThingsboardException { |
|||
switch (entityId.getEntityType()) { |
|||
case DEVICE: |
|||
return deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())) != null; |
|||
case ASSET: |
|||
return assetService.findAssetById(tenantId, new AssetId(entityId.getId())) != null; |
|||
case ENTITY_VIEW: |
|||
return entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId())) != null; |
|||
case CUSTOMER: |
|||
return customerService.findCustomerById(tenantId, new CustomerId(entityId.getId())) != null; |
|||
case USER: |
|||
return userService.findUserById(tenantId, new UserId(entityId.getId())) != null; |
|||
case DASHBOARD: |
|||
return dashboardService.findDashboardById(tenantId, new DashboardId(entityId.getId())) != null; |
|||
default: |
|||
throw new ThingsboardException("Unsupported entity type " + entityId.getEntityType(), ThingsboardErrorCode.INVALID_ARGUMENTS); |
|||
} |
|||
} |
|||
|
|||
public DownlinkMsg convertRelationEventToDownlink(EdgeEvent edgeEvent) { |
|||
EntityRelation entityRelation = JacksonUtil.OBJECT_MAPPER.convertValue(edgeEvent.getBody(), EntityRelation.class); |
|||
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); |
|||
RelationUpdateMsg relationUpdateMsg = relationMsgConstructor.constructRelationUpdatedMsg(msgType, entityRelation); |
|||
return DownlinkMsg.newBuilder() |
|||
.setDownlinkMsgId(EdgeUtils.nextPositiveInt()) |
|||
.addRelationUpdateMsg(relationUpdateMsg) |
|||
.build(); |
|||
} |
|||
|
|||
public ListenableFuture<Void> processRelationNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException { |
|||
EntityRelation relation = JacksonUtil.OBJECT_MAPPER.readValue(edgeNotificationMsg.getBody(), EntityRelation.class); |
|||
if (relation.getFrom().getEntityType().equals(EntityType.EDGE) || |
|||
relation.getTo().getEntityType().equals(EntityType.EDGE)) { |
|||
return Futures.immediateFuture(null); |
|||
} |
|||
|
|||
Set<EdgeId> uniqueEdgeIds = new HashSet<>(); |
|||
uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getTo())); |
|||
uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getFrom())); |
|||
if (uniqueEdgeIds.isEmpty()) { |
|||
return Futures.immediateFuture(null); |
|||
} |
|||
List<ListenableFuture<Void>> futures = new ArrayList<>(); |
|||
for (EdgeId edgeId : uniqueEdgeIds) { |
|||
futures.add(saveEdgeEvent(tenantId, |
|||
edgeId, |
|||
EdgeEventType.RELATION, |
|||
EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()), |
|||
null, |
|||
JacksonUtil.OBJECT_MAPPER.valueToTree(relation))); |
|||
} |
|||
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); |
|||
} |
|||
} |
|||
@ -0,0 +1,102 @@ |
|||
/** |
|||
* 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.edge.rpc.processor.alarm; |
|||
|
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Component; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.EdgeUtils; |
|||
import org.thingsboard.server.common.data.alarm.Alarm; |
|||
import org.thingsboard.server.common.data.edge.EdgeEvent; |
|||
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
|||
import org.thingsboard.server.common.data.edge.EdgeEventType; |
|||
import org.thingsboard.server.common.data.id.AlarmId; |
|||
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.gen.edge.v1.AlarmUpdateMsg; |
|||
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
@Component |
|||
@Slf4j |
|||
@TbCoreComponent |
|||
public class AlarmEdgeProcessor extends BaseAlarmProcessor { |
|||
|
|||
public DownlinkMsg convertAlarmEventToDownlink(EdgeEvent edgeEvent) { |
|||
AlarmUpdateMsg alarmUpdateMsg = |
|||
convertAlarmEventToAlarmMsg(edgeEvent.getTenantId(), edgeEvent.getEntityId(), edgeEvent.getAction(), edgeEvent.getBody()); |
|||
if (alarmUpdateMsg != null) { |
|||
return DownlinkMsg.newBuilder() |
|||
.setDownlinkMsgId(EdgeUtils.nextPositiveInt()) |
|||
.addAlarmUpdateMsg(alarmUpdateMsg) |
|||
.build(); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
public ListenableFuture<Void> processAlarmNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException { |
|||
EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); |
|||
AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); |
|||
switch (actionType) { |
|||
case DELETED: |
|||
EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB())); |
|||
Alarm deletedAlarm = JacksonUtil.OBJECT_MAPPER.readValue(edgeNotificationMsg.getBody(), Alarm.class); |
|||
return saveEdgeEvent(tenantId, edgeId, EdgeEventType.ALARM, actionType, alarmId, JacksonUtil.OBJECT_MAPPER.valueToTree(deletedAlarm)); |
|||
default: |
|||
ListenableFuture<Alarm> alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId); |
|||
return Futures.transformAsync(alarmFuture, alarm -> { |
|||
if (alarm == null) { |
|||
return Futures.immediateFuture(null); |
|||
} |
|||
EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(alarm.getOriginator().getEntityType()); |
|||
if (type == null) { |
|||
return Futures.immediateFuture(null); |
|||
} |
|||
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); |
|||
PageData<EdgeId> pageData; |
|||
List<ListenableFuture<Void>> futures = new ArrayList<>(); |
|||
do { |
|||
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, alarm.getOriginator(), pageLink); |
|||
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { |
|||
for (EdgeId relatedEdgeId : pageData.getData()) { |
|||
futures.add(saveEdgeEvent(tenantId, |
|||
relatedEdgeId, |
|||
EdgeEventType.ALARM, |
|||
EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()), |
|||
alarmId, |
|||
null)); |
|||
} |
|||
if (pageData.hasNext()) { |
|||
pageLink = pageLink.nextPageLink(); |
|||
} |
|||
} |
|||
} while (pageData != null && pageData.hasNext()); |
|||
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); |
|||
}, dbCallbackExecutorService); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,127 @@ |
|||
/** |
|||
* 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.edge.rpc.processor.alarm; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Component; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.alarm.Alarm; |
|||
import org.thingsboard.server.common.data.alarm.AlarmSeverity; |
|||
import org.thingsboard.server.common.data.alarm.AlarmStatus; |
|||
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
|||
import org.thingsboard.server.common.data.id.AlarmId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; |
|||
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
@Slf4j |
|||
public abstract class BaseAlarmProcessor extends BaseEdgeProcessor { |
|||
|
|||
public ListenableFuture<Void> processAlarmMsg(TenantId tenantId, AlarmUpdateMsg alarmUpdateMsg) { |
|||
log.trace("[{}] processAlarmMsg [{}]", tenantId, alarmUpdateMsg); |
|||
EntityId originatorId = getAlarmOriginator(tenantId, alarmUpdateMsg.getOriginatorName(), |
|||
EntityType.valueOf(alarmUpdateMsg.getOriginatorType())); |
|||
if (originatorId == null) { |
|||
log.warn("Originator not found for the alarm msg {}", alarmUpdateMsg); |
|||
return Futures.immediateFuture(null); |
|||
} |
|||
try { |
|||
Alarm existentAlarm = alarmService.findLatestByOriginatorAndType(tenantId, originatorId, alarmUpdateMsg.getType()).get(); |
|||
switch (alarmUpdateMsg.getMsgType()) { |
|||
case ENTITY_CREATED_RPC_MESSAGE: |
|||
case ENTITY_UPDATED_RPC_MESSAGE: |
|||
if (existentAlarm == null || existentAlarm.getStatus().isCleared()) { |
|||
existentAlarm = new Alarm(); |
|||
existentAlarm.setTenantId(tenantId); |
|||
existentAlarm.setType(alarmUpdateMsg.getName()); |
|||
existentAlarm.setOriginator(originatorId); |
|||
existentAlarm.setSeverity(AlarmSeverity.valueOf(alarmUpdateMsg.getSeverity())); |
|||
existentAlarm.setStartTs(alarmUpdateMsg.getStartTs()); |
|||
existentAlarm.setClearTs(alarmUpdateMsg.getClearTs()); |
|||
existentAlarm.setPropagate(alarmUpdateMsg.getPropagate()); |
|||
} |
|||
existentAlarm.setStatus(AlarmStatus.valueOf(alarmUpdateMsg.getStatus())); |
|||
existentAlarm.setAckTs(alarmUpdateMsg.getAckTs()); |
|||
existentAlarm.setEndTs(alarmUpdateMsg.getEndTs()); |
|||
existentAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails())); |
|||
alarmService.createOrUpdateAlarm(existentAlarm); |
|||
break; |
|||
case ALARM_ACK_RPC_MESSAGE: |
|||
if (existentAlarm != null) { |
|||
alarmService.ackAlarm(tenantId, existentAlarm.getId(), alarmUpdateMsg.getAckTs()); |
|||
} |
|||
break; |
|||
case ALARM_CLEAR_RPC_MESSAGE: |
|||
if (existentAlarm != null) { |
|||
alarmService.clearAlarm(tenantId, existentAlarm.getId(), |
|||
JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails()), alarmUpdateMsg.getAckTs()); |
|||
} |
|||
break; |
|||
case ENTITY_DELETED_RPC_MESSAGE: |
|||
if (existentAlarm != null) { |
|||
alarmService.deleteAlarm(tenantId, existentAlarm.getId()); |
|||
} |
|||
break; |
|||
} |
|||
return Futures.immediateFuture(null); |
|||
} catch (Exception e) { |
|||
log.error("[{}] Failed to process alarm update msg [{}]", tenantId, alarmUpdateMsg, e); |
|||
return Futures.immediateFailedFuture(e); |
|||
} |
|||
} |
|||
|
|||
private EntityId getAlarmOriginator(TenantId tenantId, String entityName, EntityType entityType) { |
|||
switch (entityType) { |
|||
case DEVICE: |
|||
return deviceService.findDeviceByTenantIdAndName(tenantId, entityName).getId(); |
|||
case ASSET: |
|||
return assetService.findAssetByTenantIdAndName(tenantId, entityName).getId(); |
|||
case ENTITY_VIEW: |
|||
return entityViewService.findEntityViewByTenantIdAndName(tenantId, entityName).getId(); |
|||
default: |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public AlarmUpdateMsg convertAlarmEventToAlarmMsg(TenantId tenantId, UUID entityId, EdgeEventActionType actionType, JsonNode body) { |
|||
AlarmId alarmId = new AlarmId(entityId); |
|||
UpdateMsgType msgType = getUpdateMsgType(actionType); |
|||
switch (actionType) { |
|||
case ADDED: |
|||
case UPDATED: |
|||
case ALARM_ACK: |
|||
case ALARM_CLEAR: |
|||
Alarm alarm = alarmService.findAlarmById(tenantId, alarmId); |
|||
if (alarm != null) { |
|||
return alarmMsgConstructor.constructAlarmUpdatedMsg(tenantId, msgType, alarm); |
|||
} |
|||
break; |
|||
case DELETED: |
|||
Alarm deletedAlarm = JacksonUtil.OBJECT_MAPPER.convertValue(body, Alarm.class); |
|||
return alarmMsgConstructor.constructAlarmUpdatedMsg(tenantId, msgType, deletedAlarm); |
|||
} |
|||
return null; |
|||
} |
|||
} |
|||
@ -0,0 +1,134 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.edge.rpc.processor.device; |
|||
|
|||
import com.datastax.oss.driver.api.core.uuid.Uuids; |
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.data.util.Pair; |
|||
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.data.DeviceData; |
|||
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.OtaPackageId; |
|||
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.gen.edge.v1.DeviceCredentialsUpdateMsg; |
|||
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; |
|||
import org.thingsboard.server.queue.util.DataDecodingEncodingService; |
|||
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; |
|||
|
|||
import java.util.Optional; |
|||
import java.util.UUID; |
|||
|
|||
@Slf4j |
|||
public abstract class BaseDeviceProcessor extends BaseEdgeProcessor { |
|||
|
|||
@Autowired |
|||
protected DataDecodingEncodingService dataDecodingEncodingService; |
|||
|
|||
protected Pair<Boolean, Boolean> saveOrUpdateDevice(TenantId tenantId, DeviceId deviceId, DeviceUpdateMsg deviceUpdateMsg, CustomerId customerId) { |
|||
boolean created = false; |
|||
boolean deviceNameUpdated = false; |
|||
deviceCreationLock.lock(); |
|||
try { |
|||
Device device = deviceService.findDeviceById(tenantId, deviceId); |
|||
String deviceName = deviceUpdateMsg.getName(); |
|||
if (device == null) { |
|||
created = true; |
|||
device = new Device(); |
|||
device.setTenantId(tenantId); |
|||
device.setCreatedTime(Uuids.unixTimestamp(deviceId.getId())); |
|||
Device deviceByName = deviceService.findDeviceByTenantIdAndName(tenantId, deviceName); |
|||
if (deviceByName != null) { |
|||
deviceName = deviceName + "_" + StringUtils.randomAlphabetic(15); |
|||
log.warn("Device with name {} already exists. Renaming device name to {}", |
|||
deviceUpdateMsg.getName(), deviceName); |
|||
deviceNameUpdated = true; |
|||
} |
|||
} |
|||
device.setName(deviceName); |
|||
device.setType(deviceUpdateMsg.getType()); |
|||
device.setLabel(deviceUpdateMsg.hasLabel() ? deviceUpdateMsg.getLabel() : null); |
|||
device.setAdditionalInfo(deviceUpdateMsg.hasAdditionalInfo() |
|||
? JacksonUtil.toJsonNode(deviceUpdateMsg.getAdditionalInfo()) : null); |
|||
|
|||
UUID deviceProfileUUID = safeGetUUID(deviceUpdateMsg.getDeviceProfileIdMSB(), deviceUpdateMsg.getDeviceProfileIdLSB()); |
|||
device.setDeviceProfileId(deviceProfileUUID != null ? new DeviceProfileId(deviceProfileUUID) : null); |
|||
|
|||
device.setCustomerId(customerId); |
|||
|
|||
Optional<DeviceData> deviceDataOpt = |
|||
dataDecodingEncodingService.decode(deviceUpdateMsg.getDeviceDataBytes().toByteArray()); |
|||
device.setDeviceData(deviceDataOpt.orElse(null)); |
|||
|
|||
UUID firmwareUUID = safeGetUUID(deviceUpdateMsg.getFirmwareIdMSB(), deviceUpdateMsg.getFirmwareIdLSB()); |
|||
device.setFirmwareId(firmwareUUID != null ? new OtaPackageId(firmwareUUID) : null); |
|||
|
|||
UUID softwareUUID = safeGetUUID(deviceUpdateMsg.getSoftwareIdMSB(), deviceUpdateMsg.getSoftwareIdLSB()); |
|||
device.setSoftwareId(softwareUUID != null ? new OtaPackageId(softwareUUID) : null); |
|||
deviceValidator.validate(device, Device::getTenantId); |
|||
if (created) { |
|||
device.setId(deviceId); |
|||
} |
|||
Device savedDevice = deviceService.saveDevice(device, false); |
|||
if (created) { |
|||
DeviceCredentials deviceCredentials = new DeviceCredentials(); |
|||
deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId())); |
|||
deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); |
|||
deviceCredentials.setCredentialsId(StringUtils.randomAlphanumeric(20)); |
|||
deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials); |
|||
} |
|||
tbClusterService.onDeviceUpdated(savedDevice, created ? null : device, false); |
|||
} finally { |
|||
deviceCreationLock.unlock(); |
|||
} |
|||
return Pair.of(created, deviceNameUpdated); |
|||
} |
|||
|
|||
public ListenableFuture<Void> processDeviceCredentialsMsg(TenantId tenantId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg) { |
|||
log.debug("[{}] Executing processDeviceCredentialsMsg, deviceCredentialsUpdateMsg [{}]", tenantId, deviceCredentialsUpdateMsg); |
|||
DeviceId deviceId = new DeviceId(new UUID(deviceCredentialsUpdateMsg.getDeviceIdMSB(), deviceCredentialsUpdateMsg.getDeviceIdLSB())); |
|||
ListenableFuture<Device> deviceFuture = deviceService.findDeviceByIdAsync(tenantId, deviceId); |
|||
return Futures.transform(deviceFuture, device -> { |
|||
if (device != null) { |
|||
log.debug("Updating device credentials for device [{}]. New device credentials Id [{}], value [{}]", |
|||
device.getName(), deviceCredentialsUpdateMsg.getCredentialsId(), deviceCredentialsUpdateMsg.getCredentialsValue()); |
|||
try { |
|||
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, device.getId()); |
|||
deviceCredentials.setCredentialsType(DeviceCredentialsType.valueOf(deviceCredentialsUpdateMsg.getCredentialsType())); |
|||
deviceCredentials.setCredentialsId(deviceCredentialsUpdateMsg.getCredentialsId()); |
|||
deviceCredentials.setCredentialsValue(deviceCredentialsUpdateMsg.hasCredentialsValue() |
|||
? deviceCredentialsUpdateMsg.getCredentialsValue() : null); |
|||
deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentials); |
|||
} catch (Exception e) { |
|||
log.error("Can't update device credentials for device [{}], deviceCredentialsUpdateMsg [{}]", |
|||
device.getName(), deviceCredentialsUpdateMsg, e); |
|||
throw new RuntimeException(e); |
|||
} |
|||
} else { |
|||
log.warn("Can't find device by id [{}], deviceCredentialsUpdateMsg [{}]", deviceId, deviceCredentialsUpdateMsg); |
|||
} |
|||
return null; |
|||
}, dbCallbackExecutorService); |
|||
} |
|||
} |
|||
@ -0,0 +1,104 @@ |
|||
/** |
|||
* 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.edge.rpc.processor.relation; |
|||
|
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.id.AssetId; |
|||
import org.thingsboard.server.common.data.id.CustomerId; |
|||
import org.thingsboard.server.common.data.id.DashboardId; |
|||
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.EntityIdFactory; |
|||
import org.thingsboard.server.common.data.id.EntityViewId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.id.UserId; |
|||
import org.thingsboard.server.common.data.relation.EntityRelation; |
|||
import org.thingsboard.server.common.data.relation.RelationTypeGroup; |
|||
import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; |
|||
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
@Slf4j |
|||
public abstract class BaseRelationProcessor extends BaseEdgeProcessor { |
|||
|
|||
public ListenableFuture<Void> processRelationMsg(TenantId tenantId, RelationUpdateMsg relationUpdateMsg) { |
|||
log.trace("[{}] processRelationFromEdge [{}]", tenantId, relationUpdateMsg); |
|||
try { |
|||
EntityRelation entityRelation = new EntityRelation(); |
|||
|
|||
UUID fromUUID = new UUID(relationUpdateMsg.getFromIdMSB(), relationUpdateMsg.getFromIdLSB()); |
|||
EntityId fromId = EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(relationUpdateMsg.getFromEntityType()), fromUUID); |
|||
entityRelation.setFrom(fromId); |
|||
|
|||
UUID toUUID = new UUID(relationUpdateMsg.getToIdMSB(), relationUpdateMsg.getToIdLSB()); |
|||
EntityId toId = EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(relationUpdateMsg.getToEntityType()), toUUID); |
|||
entityRelation.setTo(toId); |
|||
|
|||
entityRelation.setType(relationUpdateMsg.getType()); |
|||
entityRelation.setTypeGroup(relationUpdateMsg.hasTypeGroup() |
|||
? RelationTypeGroup.valueOf(relationUpdateMsg.getTypeGroup()) : RelationTypeGroup.COMMON); |
|||
entityRelation.setAdditionalInfo(JacksonUtil.toJsonNode(relationUpdateMsg.getAdditionalInfo())); |
|||
switch (relationUpdateMsg.getMsgType()) { |
|||
case ENTITY_CREATED_RPC_MESSAGE: |
|||
case ENTITY_UPDATED_RPC_MESSAGE: |
|||
if (isEntityExists(tenantId, entityRelation.getTo()) |
|||
&& isEntityExists(tenantId, entityRelation.getFrom())) { |
|||
return Futures.transform(relationService.saveRelationAsync(tenantId, entityRelation), |
|||
(result) -> null, dbCallbackExecutorService); |
|||
} else { |
|||
log.warn("Skipping relating update msg because from/to entity doesn't exists on edge, {}", relationUpdateMsg); |
|||
return Futures.immediateFuture(null); |
|||
} |
|||
case ENTITY_DELETED_RPC_MESSAGE: |
|||
return Futures.transform(relationService.deleteRelationAsync(tenantId, entityRelation), |
|||
(result) -> null, dbCallbackExecutorService); |
|||
case UNRECOGNIZED: |
|||
default: |
|||
return handleUnsupportedMsgType(relationUpdateMsg.getMsgType()); |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("[{}] Failed to process relation update msg [{}]", tenantId, relationUpdateMsg, e); |
|||
return Futures.immediateFailedFuture(e); |
|||
} |
|||
} |
|||
|
|||
private boolean isEntityExists(TenantId tenantId, EntityId entityId) { |
|||
switch (entityId.getEntityType()) { |
|||
case DEVICE: |
|||
return deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())) != null; |
|||
case ASSET: |
|||
return assetService.findAssetById(tenantId, new AssetId(entityId.getId())) != null; |
|||
case ENTITY_VIEW: |
|||
return entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId())) != null; |
|||
case CUSTOMER: |
|||
return customerService.findCustomerById(tenantId, new CustomerId(entityId.getId())) != null; |
|||
case USER: |
|||
return userService.findUserById(tenantId, new UserId(entityId.getId())) != null; |
|||
case DASHBOARD: |
|||
return dashboardService.findDashboardById(tenantId, new DashboardId(entityId.getId())) != null; |
|||
case EDGE: |
|||
return edgeService.findEdgeById(tenantId, new EdgeId(entityId.getId())) != null; |
|||
default: |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
/** |
|||
* 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.edge.rpc.processor.relation; |
|||
|
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Component; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.EdgeUtils; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.edge.EdgeEvent; |
|||
import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
|||
import org.thingsboard.server.common.data.edge.EdgeEventType; |
|||
import org.thingsboard.server.common.data.id.EdgeId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.relation.EntityRelation; |
|||
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; |
|||
import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; |
|||
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.HashSet; |
|||
import java.util.List; |
|||
import java.util.Set; |
|||
|
|||
@Component |
|||
@Slf4j |
|||
@TbCoreComponent |
|||
public class RelationEdgeProcessor extends BaseRelationProcessor { |
|||
|
|||
public DownlinkMsg convertRelationEventToDownlink(EdgeEvent edgeEvent) { |
|||
EntityRelation entityRelation = JacksonUtil.OBJECT_MAPPER.convertValue(edgeEvent.getBody(), EntityRelation.class); |
|||
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); |
|||
RelationUpdateMsg relationUpdateMsg = relationMsgConstructor.constructRelationUpdatedMsg(msgType, entityRelation); |
|||
return DownlinkMsg.newBuilder() |
|||
.setDownlinkMsgId(EdgeUtils.nextPositiveInt()) |
|||
.addRelationUpdateMsg(relationUpdateMsg) |
|||
.build(); |
|||
} |
|||
|
|||
public ListenableFuture<Void> processRelationNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException { |
|||
EntityRelation relation = JacksonUtil.OBJECT_MAPPER.readValue(edgeNotificationMsg.getBody(), EntityRelation.class); |
|||
if (relation.getFrom().getEntityType().equals(EntityType.EDGE) || |
|||
relation.getTo().getEntityType().equals(EntityType.EDGE)) { |
|||
return Futures.immediateFuture(null); |
|||
} |
|||
|
|||
Set<EdgeId> uniqueEdgeIds = new HashSet<>(); |
|||
uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getTo())); |
|||
uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getFrom())); |
|||
if (uniqueEdgeIds.isEmpty()) { |
|||
return Futures.immediateFuture(null); |
|||
} |
|||
List<ListenableFuture<Void>> futures = new ArrayList<>(); |
|||
for (EdgeId edgeId : uniqueEdgeIds) { |
|||
futures.add(saveEdgeEvent(tenantId, |
|||
edgeId, |
|||
EdgeEventType.RELATION, |
|||
EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()), |
|||
null, |
|||
JacksonUtil.OBJECT_MAPPER.valueToTree(relation))); |
|||
} |
|||
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
/** |
|||
* 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.edge.rpc.processor.telemetry; |
|||
|
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Component; |
|||
import org.thingsboard.server.common.data.DataConstants; |
|||
import org.thingsboard.server.common.data.EdgeUtils; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.edge.EdgeEvent; |
|||
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; |
|||
import org.thingsboard.server.gen.edge.v1.EntityDataProto; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
|
|||
@Component |
|||
@Slf4j |
|||
@TbCoreComponent |
|||
public class TelemetryEdgeProcessor extends BaseTelemetryProcessor { |
|||
|
|||
@Override |
|||
protected String getMsgSourceKey() { |
|||
return DataConstants.EDGE_MSG_SOURCE; |
|||
} |
|||
|
|||
public DownlinkMsg convertTelemetryEventToDownlink(EdgeEvent edgeEvent) throws JsonProcessingException { |
|||
EntityType entityType = EntityType.valueOf(edgeEvent.getType().name()); |
|||
EntityDataProto entityDataProto = convertTelemetryEventToEntityDataProto(entityType, edgeEvent.getEntityId(), |
|||
edgeEvent.getAction(), edgeEvent.getBody()); |
|||
return DownlinkMsg.newBuilder() |
|||
.setDownlinkMsgId(EdgeUtils.nextPositiveInt()) |
|||
.addEntityData(entityDataProto) |
|||
.build(); |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.entitiy.alarm; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.User; |
|||
import org.thingsboard.server.common.data.alarm.Alarm; |
|||
import org.thingsboard.server.common.data.alarm.AlarmComment; |
|||
import org.thingsboard.server.common.data.audit.ActionType; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.UserId; |
|||
import org.thingsboard.server.service.entitiy.AbstractTbEntityService; |
|||
|
|||
@Service |
|||
@AllArgsConstructor |
|||
public class DefaultTbAlarmCommentService extends AbstractTbEntityService implements TbAlarmCommentService{ |
|||
@Override |
|||
public AlarmComment saveAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) throws ThingsboardException { |
|||
ActionType actionType = alarmComment.getId() == null ? ActionType.ADDED_COMMENT : ActionType.UPDATED_COMMENT; |
|||
UserId userId = user.getId(); |
|||
alarmComment.setUserId(userId); |
|||
try { |
|||
AlarmComment savedAlarmComment = checkNotNull(alarmCommentService.createOrUpdateAlarmComment(alarm.getTenantId(), alarmComment)); |
|||
notificationEntityService.notifyAlarmComment(alarm, savedAlarmComment, actionType, user); |
|||
return savedAlarmComment; |
|||
} catch (Exception e) { |
|||
notificationEntityService.logEntityAction(alarm.getTenantId(), emptyId(EntityType.ALARM), alarm, actionType, user, e, alarmComment); |
|||
throw e; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void deleteAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) { |
|||
alarmCommentService.deleteAlarmComment(alarm.getTenantId(), alarmComment.getId()); |
|||
notificationEntityService.notifyAlarmComment(alarm, alarmComment, ActionType.DELETED_COMMENT, user); |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
/** |
|||
* 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.entitiy.alarm; |
|||
|
|||
import org.thingsboard.server.common.data.User; |
|||
import org.thingsboard.server.common.data.alarm.Alarm; |
|||
import org.thingsboard.server.common.data.alarm.AlarmComment; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
|
|||
public interface TbAlarmCommentService { |
|||
AlarmComment saveAlarmComment(Alarm alarm, AlarmComment alarmComment, User user) throws ThingsboardException; |
|||
|
|||
void deleteAlarmComment(Alarm alarm, AlarmComment alarmComment, User user); |
|||
} |
|||
@ -0,0 +1,363 @@ |
|||
/** |
|||
* 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.controller; |
|||
|
|||
import com.fasterxml.jackson.core.type.TypeReference; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.node.TextNode; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.junit.After; |
|||
import org.junit.Assert; |
|||
import org.junit.Before; |
|||
import org.junit.Test; |
|||
import org.mockito.AdditionalAnswers; |
|||
import org.mockito.Mockito; |
|||
import org.springframework.context.annotation.Bean; |
|||
import org.springframework.context.annotation.Primary; |
|||
import org.springframework.test.context.ContextConfiguration; |
|||
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.alarm.Alarm; |
|||
import org.thingsboard.server.common.data.alarm.AlarmComment; |
|||
import org.thingsboard.server.common.data.alarm.AlarmCommentInfo; |
|||
import org.thingsboard.server.common.data.alarm.AlarmCommentType; |
|||
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; |
|||
|
|||
import static org.hamcrest.Matchers.containsString; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
|
|||
@Slf4j |
|||
@ContextConfiguration(classes = {BaseAlarmCommentControllerTest.Config.class}) |
|||
public abstract class BaseAlarmCommentControllerTest extends AbstractControllerTest { |
|||
|
|||
protected Device customerDevice; |
|||
protected Alarm alarm; |
|||
|
|||
static class Config { |
|||
@Bean |
|||
@Primary |
|||
public AlarmDao alarmDao(AlarmDao alarmDao) { |
|||
return Mockito.mock(AlarmDao.class, AdditionalAnswers.delegatesTo(alarmDao)); |
|||
} |
|||
} |
|||
|
|||
@Before |
|||
public void setup() throws Exception { |
|||
loginTenantAdmin(); |
|||
|
|||
Device device = new Device(); |
|||
device.setTenantId(tenantId); |
|||
device.setName("Test device"); |
|||
device.setLabel("Label"); |
|||
device.setType("Type"); |
|||
device.setCustomerId(customerId); |
|||
customerDevice = doPost("/api/device", device, Device.class); |
|||
|
|||
alarm = Alarm.builder() |
|||
.tenantId(tenantId) |
|||
.customerId(customerId) |
|||
.originator(customerDevice.getId()) |
|||
.status(AlarmStatus.ACTIVE_UNACK) |
|||
.severity(AlarmSeverity.CRITICAL) |
|||
.type("test alarm type") |
|||
.build(); |
|||
|
|||
alarm = doPost("/api/alarm", alarm, Alarm.class); |
|||
|
|||
resetTokens(); |
|||
} |
|||
|
|||
@After |
|||
public void teardown() throws Exception { |
|||
Mockito.reset(tbClusterService, auditLogService); |
|||
loginSysAdmin(); |
|||
deleteDifferentTenant(); |
|||
} |
|||
|
|||
@Test |
|||
public void testCreateAlarmCommentViaCustomer() throws Exception { |
|||
loginCustomerUser(); |
|||
|
|||
Mockito.reset(tbClusterService, auditLogService); |
|||
|
|||
AlarmComment createdComment = createAlarmComment(alarm.getId()); |
|||
|
|||
testLogEntityAction(alarm, alarm.getId(), tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ADDED_COMMENT, 1, createdComment); |
|||
} |
|||
|
|||
@Test |
|||
public void testCreateAlarmCommentViaTenant() throws Exception { |
|||
loginTenantAdmin(); |
|||
|
|||
Mockito.reset(tbClusterService, auditLogService); |
|||
|
|||
AlarmComment createdComment = createAlarmComment(alarm.getId()); |
|||
Assert.assertEquals(AlarmCommentType.OTHER, createdComment.getType()); |
|||
|
|||
testLogEntityAction(alarm, alarm.getId(), tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ADDED_COMMENT, 1, createdComment); |
|||
} |
|||
|
|||
@Test |
|||
public void testUpdateAlarmCommentViaCustomer() throws Exception { |
|||
loginCustomerUser(); |
|||
AlarmComment savedComment = createAlarmComment(alarm.getId()); |
|||
|
|||
Mockito.reset(tbClusterService, auditLogService); |
|||
|
|||
JsonNode newComment = JacksonUtil.newObjectNode().set("text", new TextNode("Updated comment")); |
|||
savedComment.setComment(newComment); |
|||
AlarmComment updatedAlarmComment = saveAlarmComment(alarm.getId(), savedComment); |
|||
|
|||
Assert.assertNotNull(updatedAlarmComment); |
|||
Assert.assertEquals(newComment.get("text"), updatedAlarmComment.getComment().get("text")); |
|||
Assert.assertEquals("true", updatedAlarmComment.getComment().get("edited").asText()); |
|||
Assert.assertNotNull(updatedAlarmComment.getComment().get("editedOn")); |
|||
|
|||
testLogEntityAction(alarm, alarm.getId(), tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.UPDATED_COMMENT, 1, savedComment); |
|||
} |
|||
|
|||
@Test |
|||
public void testUpdateAlarmViaTenant() throws Exception { |
|||
loginTenantAdmin(); |
|||
AlarmComment savedComment = createAlarmComment(alarm.getId()); |
|||
|
|||
Mockito.reset(tbClusterService, auditLogService); |
|||
|
|||
JsonNode newComment = JacksonUtil.newObjectNode().set("text", new TextNode("Updated comment")); |
|||
savedComment.setComment(newComment); |
|||
AlarmComment updatedAlarmComment = saveAlarmComment(alarm.getId(), savedComment); |
|||
|
|||
Assert.assertNotNull(updatedAlarmComment); |
|||
Assert.assertEquals(newComment.get("text"), updatedAlarmComment.getComment().get("text")); |
|||
Assert.assertEquals("true", updatedAlarmComment.getComment().get("edited").asText()); |
|||
Assert.assertNotNull(updatedAlarmComment.getComment().get("editedOn")); |
|||
|
|||
testLogEntityAction(alarm, alarm.getId(), tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.UPDATED_COMMENT, 1, updatedAlarmComment); |
|||
} |
|||
|
|||
@Test |
|||
public void testUpdateAlarmViaDifferentTenant() throws Exception { |
|||
loginTenantAdmin(); |
|||
AlarmComment savedComment = createAlarmComment(alarm.getId()); |
|||
|
|||
loginDifferentTenant(); |
|||
|
|||
Mockito.reset(tbClusterService, auditLogService); |
|||
JsonNode newComment = JacksonUtil.newObjectNode().set("text", new TextNode("Updated comment")); |
|||
savedComment.setComment(newComment); |
|||
|
|||
doPost("/api/alarm/" + alarm.getId() + "/comment", savedComment) |
|||
.andExpect(status().isForbidden()) |
|||
.andExpect(statusReason(containsString(msgErrorPermission))); |
|||
|
|||
testNotifyEntityNever(alarm.getId(), savedComment); |
|||
} |
|||
|
|||
@Test |
|||
public void testUpdateAlarmViaDifferentCustomer() throws Exception { |
|||
loginCustomerUser(); |
|||
AlarmComment savedComment = createAlarmComment(alarm.getId()); |
|||
|
|||
loginDifferentCustomer(); |
|||
|
|||
Mockito.reset(tbClusterService, auditLogService); |
|||
JsonNode newComment = JacksonUtil.newObjectNode().set("text", new TextNode("Updated comment")); |
|||
savedComment.setComment(newComment); |
|||
|
|||
doPost("/api/alarm/" + alarm.getId() + "/comment", savedComment) |
|||
.andExpect(status().isForbidden()) |
|||
.andExpect(statusReason(containsString(msgErrorPermission))); |
|||
|
|||
testNotifyEntityNever(alarm.getId(), savedComment); |
|||
} |
|||
|
|||
@Test |
|||
public void testDeleteAlarmСommentViaCustomer() throws Exception { |
|||
loginCustomerUser(); |
|||
AlarmComment alarmComment = createAlarmComment(alarm.getId()); |
|||
|
|||
Mockito.reset(tbClusterService, auditLogService); |
|||
|
|||
doDelete("/api/alarm/" + alarm.getId() + "/comment/" + alarmComment.getId()) |
|||
.andExpect(status().isOk()); |
|||
|
|||
testLogEntityAction(alarm, alarm.getId(), tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.DELETED_COMMENT, 1, alarmComment); |
|||
} |
|||
|
|||
@Test |
|||
public void testDeleteAlarmViaTenant() throws Exception { |
|||
loginTenantAdmin(); |
|||
AlarmComment alarmComment = createAlarmComment(alarm.getId()); |
|||
|
|||
Mockito.reset(tbClusterService, auditLogService); |
|||
|
|||
doDelete("/api/alarm/" + alarm.getId() + "/comment/" + alarmComment.getId()) |
|||
.andExpect(status().isOk()); |
|||
|
|||
testLogEntityAction(alarm, alarm.getId(), tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.DELETED_COMMENT, 1, alarmComment); |
|||
} |
|||
|
|||
@Test |
|||
public void testDeleteAlarmViaDifferentTenant() throws Exception { |
|||
loginTenantAdmin(); |
|||
AlarmComment alarmComment = createAlarmComment(alarm.getId()); |
|||
|
|||
loginDifferentTenant(); |
|||
|
|||
Mockito.reset(tbClusterService, auditLogService); |
|||
|
|||
doDelete("/api/alarm/" + alarm.getId() + "/comment/" + alarmComment.getId()) |
|||
.andExpect(status().isForbidden()) |
|||
.andExpect(statusReason(containsString(msgErrorPermission))); |
|||
|
|||
testNotifyEntityNever(alarm.getId(), alarm); |
|||
} |
|||
|
|||
@Test |
|||
public void testDeleteAlarmViaDifferentCustomer() throws Exception { |
|||
loginCustomerUser(); |
|||
AlarmComment alarmComment = createAlarmComment(alarm.getId()); |
|||
|
|||
loginDifferentCustomer(); |
|||
|
|||
Mockito.reset(tbClusterService, auditLogService); |
|||
|
|||
doDelete("/api/alarm/" + alarm.getId() + "/comment/" + alarmComment.getId()) |
|||
.andExpect(status().isForbidden()) |
|||
.andExpect(statusReason(containsString(msgErrorPermission))); |
|||
|
|||
testNotifyEntityNever(alarm.getId(), alarm); |
|||
} |
|||
|
|||
@Test |
|||
public void testFindAlarmCommentsViaCustomerUser() throws Exception { |
|||
loginCustomerUser(); |
|||
|
|||
List<AlarmComment> createdAlarmComments = new LinkedList<>(); |
|||
|
|||
final int size = 10; |
|||
for (int i = 0; i < size; i++) { |
|||
createdAlarmComments.add( |
|||
createAlarmComment(alarm.getId(), RandomStringUtils.randomAlphanumeric(10)) |
|||
); |
|||
} |
|||
|
|||
var response = doGetTyped( |
|||
"/api/alarm/" + alarm.getId() + "/comment?page=0&pageSize=" + size, |
|||
new TypeReference<PageData<AlarmCommentInfo>>() {} |
|||
); |
|||
var foundAlarmCommentInfos = response.getData(); |
|||
Assert.assertNotNull("Found pageData is null", foundAlarmCommentInfos); |
|||
Assert.assertNotEquals( |
|||
"Expected alarms are not found!", |
|||
0, foundAlarmCommentInfos.size() |
|||
); |
|||
|
|||
boolean allMatch = createdAlarmComments.stream() |
|||
.allMatch(alarmComment -> foundAlarmCommentInfos.stream() |
|||
.map(AlarmCommentInfo::getComment) |
|||
.anyMatch(comment -> alarmComment.getComment().equals(comment)) |
|||
); |
|||
Assert.assertTrue("Created alarm comment doesn't match any found!", allMatch); |
|||
} |
|||
|
|||
@Test |
|||
public void testFindAlarmsViaDifferentCustomerUser() throws Exception { |
|||
loginCustomerUser(); |
|||
|
|||
final int size = 10; |
|||
List<AlarmComment> createdAlarmComments = new LinkedList<>(); |
|||
for (int i = 0; i < size; i++) { |
|||
createdAlarmComments.add( |
|||
createAlarmComment(alarm.getId(), RandomStringUtils.randomAlphanumeric(10)) |
|||
); |
|||
} |
|||
|
|||
loginDifferentCustomer(); |
|||
doGet("/api/alarm/" + alarm.getId() + "/comment?page=0&pageSize=" + size) |
|||
.andExpect(status().isForbidden()) |
|||
.andExpect(statusReason(containsString(msgErrorPermission))); |
|||
} |
|||
|
|||
@Test |
|||
public void testFindAlarmCommentsViaPublicCustomer() throws Exception { |
|||
loginTenantAdmin(); |
|||
|
|||
Device device = new Device(); |
|||
device.setName("Test Public Device"); |
|||
device.setLabel("Label"); |
|||
device.setCustomerId(customerId); |
|||
device = doPost("/api/device", device, Device.class); |
|||
device = doPost("/api/customer/public/device/" + device.getUuidId(), Device.class); |
|||
|
|||
String publicId = device.getCustomerId().toString(); |
|||
|
|||
Alarm alarm = Alarm.builder() |
|||
.originator(device.getId()) |
|||
.status(AlarmStatus.ACTIVE_UNACK) |
|||
.severity(AlarmSeverity.CRITICAL) |
|||
.type("Test") |
|||
.build(); |
|||
|
|||
alarm = doPost("/api/alarm", alarm, Alarm.class); |
|||
|
|||
Mockito.reset(tbClusterService, auditLogService); |
|||
AlarmComment alarmComment = createAlarmComment(alarm.getId()); |
|||
|
|||
resetTokens(); |
|||
|
|||
JsonNode publicLoginRequest = JacksonUtil.toJsonNode("{\"publicId\": \"" + publicId + "\"}"); |
|||
JsonNode tokens = doPost("/api/auth/login/public", publicLoginRequest, JsonNode.class); |
|||
this.token = tokens.get("token").asText(); |
|||
|
|||
PageData<AlarmCommentInfo> pageData = doGetTyped( |
|||
"/api/alarm/" + alarm.getId() + "/comment" + "?page=0&pageSize=1", new TypeReference<PageData<AlarmCommentInfo>>() {} |
|||
); |
|||
|
|||
Assert.assertNotNull("Found pageData is null", pageData); |
|||
Assert.assertNotEquals("Expected alarms are not found!", 0, pageData.getTotalElements()); |
|||
|
|||
AlarmCommentInfo alarmCommentInfo = pageData.getData().get(0); |
|||
boolean equals = alarmComment.getId().equals(alarmCommentInfo.getId()) && alarmComment.getComment().equals(alarmCommentInfo.getComment()); |
|||
Assert.assertTrue("Created alarm doesn't match the found one!", equals); |
|||
} |
|||
|
|||
private AlarmComment createAlarmComment(AlarmId alarmId, String text) { |
|||
AlarmComment alarmComment = AlarmComment.builder() |
|||
.comment(JacksonUtil.newObjectNode().set("text", new TextNode(text))) |
|||
.build(); |
|||
|
|||
return saveAlarmComment(alarmId, alarmComment); |
|||
} |
|||
private AlarmComment createAlarmComment(AlarmId alarmId) { |
|||
return createAlarmComment(alarmId, "Please take a look"); |
|||
} |
|||
private AlarmComment saveAlarmComment(AlarmId alarmId, AlarmComment alarmComment) { |
|||
alarmComment = doPost("/api/alarm/" + alarmId + "/comment", alarmComment, AlarmComment.class); |
|||
Assert.assertNotNull(alarmComment); |
|||
|
|||
return alarmComment; |
|||
} |
|||
} |
|||
@ -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.controller.sql; |
|||
|
|||
import org.thingsboard.server.controller.BaseAlarmCommentControllerTest; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
@DaoSqlTest |
|||
public class AlarmCommentControllerSqlTest extends BaseAlarmCommentControllerTest { |
|||
} |
|||
@ -0,0 +1,96 @@ |
|||
/** |
|||
* 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.entitiy.alarmComment; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.junit.Test; |
|||
import org.junit.runner.RunWith; |
|||
import org.mockito.Mockito; |
|||
import org.springframework.boot.test.mock.mockito.MockBean; |
|||
import org.springframework.boot.test.mock.mockito.SpyBean; |
|||
import org.springframework.test.context.ContextConfiguration; |
|||
import org.springframework.test.context.TestPropertySource; |
|||
import org.springframework.test.context.junit4.SpringRunner; |
|||
import org.thingsboard.server.cluster.TbClusterService; |
|||
import org.thingsboard.server.common.data.User; |
|||
import org.thingsboard.server.common.data.alarm.Alarm; |
|||
import org.thingsboard.server.common.data.alarm.AlarmComment; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.AlarmCommentId; |
|||
import org.thingsboard.server.common.data.id.AlarmId; |
|||
import org.thingsboard.server.dao.alarm.AlarmCommentService; |
|||
import org.thingsboard.server.dao.alarm.AlarmService; |
|||
import org.thingsboard.server.dao.customer.CustomerService; |
|||
import org.thingsboard.server.service.entitiy.TbNotificationEntityService; |
|||
import org.thingsboard.server.service.entitiy.alarm.DefaultTbAlarmCommentService; |
|||
import org.thingsboard.server.service.executors.DbCallbackExecutorService; |
|||
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
import static org.mockito.ArgumentMatchers.any; |
|||
import static org.mockito.ArgumentMatchers.eq; |
|||
import static org.mockito.Mockito.doNothing; |
|||
import static org.mockito.Mockito.times; |
|||
import static org.mockito.Mockito.verify; |
|||
import static org.mockito.Mockito.when; |
|||
|
|||
@Slf4j |
|||
@RunWith(SpringRunner.class) |
|||
@ContextConfiguration(classes = DefaultTbAlarmCommentService.class) |
|||
@TestPropertySource(properties = { |
|||
"server.log_controller_error_stack_trace=false" |
|||
}) |
|||
public class DefaultTbAlarmCommentServiceTest { |
|||
|
|||
@MockBean |
|||
protected DbCallbackExecutorService dbExecutor; |
|||
@MockBean |
|||
protected TbNotificationEntityService notificationEntityService; |
|||
@MockBean |
|||
protected AlarmService alarmService; |
|||
@MockBean |
|||
protected AlarmCommentService alarmCommentService; |
|||
@MockBean |
|||
protected AlarmSubscriptionService alarmSubscriptionService; |
|||
@MockBean |
|||
protected CustomerService customerService; |
|||
@MockBean |
|||
protected TbClusterService tbClusterService; |
|||
@SpyBean |
|||
DefaultTbAlarmCommentService service; |
|||
|
|||
@Test |
|||
public void testSave() throws ThingsboardException { |
|||
var alarm = new Alarm(); |
|||
var alarmComment = new AlarmComment(); |
|||
when(alarmCommentService.createOrUpdateAlarmComment(Mockito.any(), eq(alarmComment))).thenReturn(alarmComment); |
|||
service.saveAlarmComment(alarm, alarmComment, new User()); |
|||
|
|||
verify(notificationEntityService, times(1)).notifyAlarmComment(any(), any(), any(), any()); |
|||
} |
|||
|
|||
@Test |
|||
public void testDelete() { |
|||
var alarmId = new AlarmId(UUID.randomUUID()); |
|||
var alarmCommentId = new AlarmCommentId(UUID.randomUUID()); |
|||
|
|||
doNothing().when(alarmCommentService).deleteAlarmComment(Mockito.any(), eq(alarmCommentId)); |
|||
service.deleteAlarmComment(new Alarm(alarmId), new AlarmComment(alarmCommentId), new User()); |
|||
|
|||
verify(notificationEntityService, times(1)).notifyAlarmComment(any(), any(), any(), any()); |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.alarm; |
|||
|
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import org.thingsboard.server.common.data.alarm.AlarmComment; |
|||
import org.thingsboard.server.common.data.alarm.AlarmCommentInfo; |
|||
import org.thingsboard.server.common.data.id.AlarmCommentId; |
|||
import org.thingsboard.server.common.data.id.AlarmId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
|
|||
public interface AlarmCommentService { |
|||
AlarmComment createOrUpdateAlarmComment(TenantId tenantId, AlarmComment alarmComment); |
|||
|
|||
void deleteAlarmComment(TenantId tenantId, AlarmCommentId alarmCommentId); |
|||
|
|||
PageData<AlarmCommentInfo> findAlarmComments(TenantId tenantId, AlarmId alarmId, PageLink pageLink); |
|||
|
|||
ListenableFuture<AlarmComment> findAlarmCommentByIdAsync(TenantId tenantId, AlarmCommentId alarmCommentId); |
|||
|
|||
AlarmComment findAlarmCommentById(TenantId tenantId, AlarmCommentId alarmCommentId); |
|||
|
|||
} |
|||
@ -0,0 +1,87 @@ |
|||
/** |
|||
* 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.alarm; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.BaseData; |
|||
import org.thingsboard.server.common.data.HasName; |
|||
import org.thingsboard.server.common.data.id.AlarmCommentId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.UserId; |
|||
import org.thingsboard.server.common.data.validation.Length; |
|||
import org.thingsboard.server.common.data.validation.NoXss; |
|||
|
|||
@ApiModel |
|||
@Data |
|||
@Builder |
|||
@AllArgsConstructor |
|||
public class AlarmComment extends BaseData<AlarmCommentId> implements HasName { |
|||
@ApiModelProperty(position = 3, value = "JSON object with Alarm id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) |
|||
private EntityId alarmId; |
|||
@ApiModelProperty(position = 4, value = "JSON object with User id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) |
|||
private UserId userId; |
|||
@ApiModelProperty(position = 5, value = "Defines origination of comment. System type means comment was created by TB. OTHER type means comment was created by user.", example = "SYSTEM/OTHER", accessMode = ApiModelProperty.AccessMode.READ_ONLY) |
|||
private AlarmCommentType type; |
|||
@ApiModelProperty(position = 6, value = "JSON object with text of comment.", dataType = "com.fasterxml.jackson.databind.JsonNode") |
|||
@NoXss |
|||
@Length(fieldName = "comment", max = 10000) |
|||
private transient JsonNode comment; |
|||
|
|||
@ApiModelProperty(position = 1, value = "JSON object with the alarm comment Id. " + |
|||
"Specify this field to update the alarm comment. " + |
|||
"Referencing non-existing alarm Id will cause error. " + |
|||
"Omit this field to create new alarm." ) |
|||
@Override |
|||
public AlarmCommentId getId() { |
|||
return super.getId(); |
|||
} |
|||
|
|||
@ApiModelProperty(position = 2, value = "Timestamp of the alarm comment creation, in milliseconds", example = "1634058704567", accessMode = ApiModelProperty.AccessMode.READ_ONLY) |
|||
@Override |
|||
public long getCreatedTime() { |
|||
return super.getCreatedTime(); |
|||
} |
|||
|
|||
public AlarmComment() { |
|||
super(); |
|||
} |
|||
|
|||
public AlarmComment(AlarmCommentId id) { |
|||
super(id); |
|||
} |
|||
|
|||
@Override |
|||
@JsonProperty(access = JsonProperty.Access.READ_ONLY) |
|||
@ApiModelProperty(position = 5, required = true, value = "representing comment text", example = "Please take a look") |
|||
public String getName() { |
|||
return comment.toString(); |
|||
} |
|||
|
|||
public AlarmComment(AlarmComment alarmComment) { |
|||
super(alarmComment.getId()); |
|||
this.createdTime = alarmComment.getCreatedTime(); |
|||
this.alarmId = alarmComment.getAlarmId(); |
|||
this.type = alarmComment.getType(); |
|||
this.comment = alarmComment.getComment(); |
|||
this.userId = alarmComment.getUserId(); |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.data.alarm; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
|
|||
@ApiModel |
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class AlarmCommentInfo extends AlarmComment { |
|||
private static final long serialVersionUID = 2807343093519543377L; |
|||
|
|||
@ApiModelProperty(position = 19, value = "User first name", example = "John") |
|||
private String firstName; |
|||
|
|||
@ApiModelProperty(position = 19, value = "User last name", example = "Brown") |
|||
private String lastName; |
|||
|
|||
@ApiModelProperty(position = 19, value = "User email address", example = "johnBrown@gmail.com") |
|||
private String email; |
|||
|
|||
public AlarmCommentInfo() { |
|||
super(); |
|||
} |
|||
|
|||
public AlarmCommentInfo(AlarmComment alarmComment) { |
|||
super(alarmComment); |
|||
} |
|||
|
|||
public AlarmCommentInfo(AlarmComment alarmComment, String firstName, String lastName, String email) { |
|||
super(alarmComment); |
|||
this.firstName = firstName; |
|||
this.lastName = lastName; |
|||
this.email = email; |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
/** |
|||
* 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.alarm; |
|||
|
|||
public enum AlarmCommentType { |
|||
|
|||
SYSTEM, OTHER; |
|||
|
|||
} |
|||
@ -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.common.data.edge; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
@ApiModel |
|||
@Data |
|||
@AllArgsConstructor |
|||
@NoArgsConstructor |
|||
public class EdgeInstallInstructions { |
|||
|
|||
@ApiModelProperty(position = 1, value = "Markdown with docker install instructions") |
|||
private String dockerInstallInstructions; |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
/** |
|||
* Copyright © 2016-2022 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.data.id; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonCreator; |
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
@ApiModel |
|||
public class AlarmCommentId extends UUIDBased { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@JsonCreator |
|||
public AlarmCommentId(@JsonProperty("id") UUID id) { |
|||
super(id); |
|||
} |
|||
|
|||
public static AlarmCommentId fromString(String commentId) { |
|||
return new AlarmCommentId(UUID.fromString(commentId)); |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue