Browse Source

Merge pull request #14118 from dashevchenko/nameConflictStrategy

Entity name conflict strategy
pull/14156/head
Viacheslav Klimov 12 months ago
committed by GitHub
parent
commit
423255e0ca
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 16
      application/src/main/java/org/thingsboard/server/controller/AssetController.java
  2. 14
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  3. 16
      application/src/main/java/org/thingsboard/server/controller/CustomerController.java
  4. 27
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  5. 16
      application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
  6. 5
      application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java
  7. 8
      application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java
  8. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/asset/TbAssetService.java
  9. 8
      application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java
  10. 4
      application/src/main/java/org/thingsboard/server/service/entitiy/customer/TbCustomerService.java
  11. 15
      application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java
  12. 5
      application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java
  13. 8
      application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java
  14. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/entityview/TbEntityViewService.java
  15. 24
      application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java
  16. 22
      application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java
  17. 24
      application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java
  18. 25
      application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java
  19. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
  20. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java
  21. 5
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
  22. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java
  23. 23
      common/data/src/main/java/org/thingsboard/server/common/data/NameConflictPolicy.java
  24. 25
      common/data/src/main/java/org/thingsboard/server/common/data/NameConflictStrategy.java
  25. 23
      common/data/src/main/java/org/thingsboard/server/common/data/UniquifyStrategy.java
  26. 5
      dao/src/main/java/org/thingsboard/server/dao/Dao.java
  27. 20
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  28. 26
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  29. 33
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  30. 44
      dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java
  31. 20
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
  32. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java
  33. 7
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
  34. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java
  35. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java
  36. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java
  37. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
  38. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java
  39. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java
  40. 9
      ui-ngx/src/app/core/http/asset.service.ts
  41. 9
      ui-ngx/src/app/core/http/customer.service.ts
  42. 18
      ui-ngx/src/app/core/http/device.service.ts
  43. 9
      ui-ngx/src/app/core/http/entity-view.service.ts
  44. 34
      ui-ngx/src/app/core/http/http-utils.ts
  45. 2
      ui-ngx/src/app/core/interceptors/interceptor-http-params.ts
  46. 6
      ui-ngx/src/app/shared/models/device.models.ts
  47. 16
      ui-ngx/src/app/shared/models/entity.models.ts

16
application/src/main/java/org/thingsboard/server/controller/AssetController.java

@ -34,6 +34,9 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.NameConflictPolicy;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.UniquifyStrategy;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo;
import org.thingsboard.server.common.data.asset.AssetSearchQuery;
@ -76,6 +79,8 @@ import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_
import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.NAME_CONFLICT_POLICY_DESC;
import static org.thingsboard.server.controller.ControllerConstants.UNIQUIFY_SEPARATOR_DESC;
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;
@ -83,6 +88,7 @@ import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_D
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.UNIQUIFY_STRATEGY_DESC;
import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK;
import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
@ -137,10 +143,16 @@ public class AssetController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/asset", method = RequestMethod.POST)
@ResponseBody
public Asset saveAsset(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the asset.") @RequestBody Asset asset) throws Exception {
public Asset saveAsset(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the asset.") @RequestBody Asset asset,
@Parameter(description = NAME_CONFLICT_POLICY_DESC)
@RequestParam(name = "nameConflictPolicy", defaultValue = "FAIL") NameConflictPolicy nameConflictPolicy,
@Parameter(description = UNIQUIFY_SEPARATOR_DESC)
@RequestParam(name = "uniquifySeparator", defaultValue = "_") String uniquifySeparator,
@Parameter(description = UNIQUIFY_STRATEGY_DESC)
@RequestParam(name = "uniquifyStrategy", defaultValue = "RANDOM") UniquifyStrategy uniquifyStrategy) throws Exception {
asset.setTenantId(getTenantId());
checkEntity(asset.getId(), asset, Resource.ASSET);
return tbAssetService.save(asset, getCurrentUser());
return tbAssetService.save(asset, new NameConflictStrategy(nameConflictPolicy, uniquifySeparator, uniquifyStrategy), getCurrentUser());
}
@ApiOperation(value = "Delete asset (deleteAsset)",

14
application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java

@ -1744,4 +1744,18 @@ public class ControllerConstants {
MARKDOWN_CODE_BLOCK_END ;
protected static final String SECURITY_WRITE_CHECK = " Security check is performed to verify that the user has 'WRITE' permission for the entity (entities).";
public static final String NAME_CONFLICT_POLICY_DESC = "Optional value of name conflict policy. Possible values: FAIL or UNIQUIFY. " +
" If omitted, FAIL policy is applied. FAIL policy implies exception will be thrown if an entity with the same name already exists. " +
" UNIQUIFY policy appends a suffix to the entity name, if a name conflict occurs.";
public static final String UNIQUIFY_SEPARATOR_DESC = "Optional value of name suffix separator used by UNIQUIFY policy. By default, underscore separator is used. " +
"For example, strategy is UNIQUIFY, separator is '-'; if a name conflict occurs for entity name 'test-name', " +
"created entity will have name like 'test-name-7fsh4f'.";
public static final String UNIQUIFY_STRATEGY_DESC = "Optional value of uniquify strategy used by UNIQUIFY policy. Possible values: RANDOM or INCREMENTAL. " +
"By default, RANDOM strategy is used, which means random alphanumeric string will be added as a suffix to entity name. " +
"INCREMENTAL implies the first possible number starting from 1 will be added as a name suffix. " +
"For example, strategy is UNIQUIFY, uniquify strategy is INCREMENTAL; if a name conflict occurs for entity name 'test-name', " +
"created entity will have name like 'test-name-1.";
}

16
application/src/main/java/org/thingsboard/server/controller/CustomerController.java

@ -32,6 +32,9 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.NameConflictPolicy;
import org.thingsboard.server.common.data.UniquifyStrategy;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@ -47,6 +50,8 @@ import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID;
import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_TEXT_SEARCH_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.HOME_DASHBOARD;
import static org.thingsboard.server.controller.ControllerConstants.NAME_CONFLICT_POLICY_DESC;
import static org.thingsboard.server.controller.ControllerConstants.UNIQUIFY_SEPARATOR_DESC;
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;
@ -54,6 +59,7 @@ import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_D
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.UNIQUIFY_STRATEGY_DESC;
import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK;
@RestController
@ -128,10 +134,16 @@ public class CustomerController extends BaseController {
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer", method = RequestMethod.POST)
@ResponseBody
public Customer saveCustomer(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the customer.") @RequestBody Customer customer) throws Exception {
public Customer saveCustomer(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the customer.") @RequestBody Customer customer,
@Parameter(description = NAME_CONFLICT_POLICY_DESC)
@RequestParam(name = "nameConflictPolicy", defaultValue = "FAIL") NameConflictPolicy nameConflictPolicy,
@Parameter(description = UNIQUIFY_SEPARATOR_DESC)
@RequestParam(name = "uniquifySeparator", defaultValue = "_") String uniquifySeparator,
@Parameter(description = UNIQUIFY_STRATEGY_DESC)
@RequestParam(name = "uniquifyStrategy", defaultValue = "RANDOM") UniquifyStrategy uniquifyStrategy) throws Exception {
customer.setTenantId(getTenantId());
checkEntity(customer.getId(), customer, Resource.CUSTOMER);
return tbCustomerService.save(customer, getCurrentUser());
return tbCustomerService.save(customer, new NameConflictStrategy(nameConflictPolicy, uniquifySeparator, uniquifyStrategy), getCurrentUser());
}
@ApiOperation(value = "Delete Customer (deleteCustomer)",

27
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -46,8 +46,11 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.DeviceInfoFilter;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.NameConflictPolicy;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.UniquifyStrategy;
import org.thingsboard.server.common.data.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
@ -108,6 +111,8 @@ import static org.thingsboard.server.controller.ControllerConstants.EDGE_ASSIGN_
import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.NAME_CONFLICT_POLICY_DESC;
import static org.thingsboard.server.controller.ControllerConstants.UNIQUIFY_SEPARATOR_DESC;
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;
@ -117,6 +122,7 @@ import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHO
import static org.thingsboard.server.controller.ControllerConstants.TENANT_ID;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.UNIQUIFY_STRATEGY_DESC;
import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK;
import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
@ -177,14 +183,21 @@ public class DeviceController extends BaseController {
@ResponseBody
public Device saveDevice(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the device.") @RequestBody Device device,
@Parameter(description = "Optional value of the device credentials to be used during device creation. " +
"If omitted, access token will be auto-generated.") @RequestParam(name = "accessToken", required = false) String accessToken) throws Exception {
"If omitted, access token will be auto-generated.")
@RequestParam(name = "accessToken", required = false) String accessToken,
@Parameter(description = NAME_CONFLICT_POLICY_DESC)
@RequestParam(name = "nameConflictPolicy", defaultValue = "FAIL") NameConflictPolicy nameConflictPolicy,
@Parameter(description = UNIQUIFY_SEPARATOR_DESC)
@RequestParam(name = "uniquifySeparator", defaultValue = "_") String uniquifySeparator,
@Parameter(description = UNIQUIFY_STRATEGY_DESC)
@RequestParam(name = "uniquifyStrategy", defaultValue = "RANDOM") UniquifyStrategy uniquifyStrategy) throws Exception {
device.setTenantId(getCurrentUser().getTenantId());
if (device.getId() != null) {
checkDeviceId(device.getId(), Operation.WRITE);
} else {
checkEntity(null, device, Resource.DEVICE);
}
return tbDeviceService.save(device, accessToken, getCurrentUser());
return tbDeviceService.save(device, accessToken, new NameConflictStrategy(nameConflictPolicy, uniquifySeparator, uniquifyStrategy), getCurrentUser());
}
@ApiOperation(value = "Create Device (saveDevice) with credentials ",
@ -209,12 +222,18 @@ public class DeviceController extends BaseController {
@RequestMapping(value = "/device-with-credentials", method = RequestMethod.POST)
@ResponseBody
public Device saveDeviceWithCredentials(@Parameter(description = "The JSON object with device and credentials. See method description above for example.")
@Valid @RequestBody SaveDeviceWithCredentialsRequest deviceAndCredentials) throws ThingsboardException {
@Valid @RequestBody SaveDeviceWithCredentialsRequest deviceAndCredentials,
@Parameter(description = NAME_CONFLICT_POLICY_DESC)
@RequestParam(name = "nameConflictPolicy", defaultValue = "FAIL") NameConflictPolicy nameConflictPolicy,
@Parameter(description = UNIQUIFY_SEPARATOR_DESC)
@RequestParam(name = "uniquifySeparator", defaultValue = "_") String uniquifySeparator,
@Parameter(description = UNIQUIFY_STRATEGY_DESC)
@RequestParam(name = "uniquifyStrategy", defaultValue = "RANDOM") UniquifyStrategy uniquifyStrategy) throws ThingsboardException {
Device device = deviceAndCredentials.getDevice();
DeviceCredentials credentials = deviceAndCredentials.getCredentials();
device.setTenantId(getCurrentUser().getTenantId());
checkEntity(device.getId(), device, Resource.DEVICE);
return tbDeviceService.saveDeviceWithCredentials(device, credentials, getCurrentUser());
return tbDeviceService.saveDeviceWithCredentials(device, credentials, new NameConflictStrategy(nameConflictPolicy, uniquifySeparator, uniquifyStrategy), getCurrentUser());
}
@ApiOperation(value = "Delete device (deleteDevice)",

16
application/src/main/java/org/thingsboard/server/controller/EntityViewController.java

@ -34,6 +34,9 @@ import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo;
import org.thingsboard.server.common.data.NameConflictPolicy;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.UniquifyStrategy;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
import org.thingsboard.server.common.data.exception.ThingsboardException;
@ -69,6 +72,8 @@ import static org.thingsboard.server.controller.ControllerConstants.ENTITY_VIEW_
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_VIEW_TEXT_SEARCH_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_VIEW_TYPE;
import static org.thingsboard.server.controller.ControllerConstants.MODEL_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.NAME_CONFLICT_POLICY_DESC;
import static org.thingsboard.server.controller.ControllerConstants.UNIQUIFY_SEPARATOR_DESC;
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;
@ -76,6 +81,7 @@ import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_D
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.UNIQUIFY_STRATEGY_DESC;
import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
/**
@ -128,7 +134,13 @@ public class EntityViewController extends BaseController {
@ResponseBody
public EntityView saveEntityView(
@Parameter(description = "A JSON object representing the entity view.")
@RequestBody EntityView entityView) throws Exception {
@RequestBody EntityView entityView,
@Parameter(description = NAME_CONFLICT_POLICY_DESC)
@RequestParam(name = "nameConflictPolicy", defaultValue = "FAIL") NameConflictPolicy nameConflictPolicy,
@Parameter(description = UNIQUIFY_SEPARATOR_DESC)
@RequestParam(name = "uniquifySeparator", defaultValue = "_") String uniquifySeparator,
@Parameter(description = UNIQUIFY_STRATEGY_DESC)
@RequestParam(name = "uniquifyStrategy", defaultValue = "RANDOM") UniquifyStrategy uniquifyStrategy) throws Exception {
entityView.setTenantId(getCurrentUser().getTenantId());
EntityView existingEntityView = null;
if (entityView.getId() == null) {
@ -137,7 +149,7 @@ public class EntityViewController extends BaseController {
} else {
existingEntityView = checkEntityViewId(entityView.getId(), Operation.WRITE);
}
return tbEntityViewService.save(entityView, existingEntityView, getCurrentUser());
return tbEntityViewService.save(entityView, existingEntityView, new NameConflictStrategy(nameConflictPolicy, uniquifySeparator, uniquifyStrategy), getCurrentUser());
}
@ApiOperation(value = "Delete entity view (deleteEntityView)",

5
application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java

@ -27,6 +27,8 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.NameConflictPolicy;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MServerSecurityConfigDefault;
import org.thingsboard.server.common.data.exception.ThingsboardException;
@ -37,6 +39,7 @@ import org.thingsboard.server.service.lwm2m.LwM2MService;
import java.util.Map;
import static org.thingsboard.server.common.data.NameConflictStrategy.DEFAULT;
import static org.thingsboard.server.controller.ControllerConstants.IS_BOOTSTRAP_SERVER_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH;
@ -73,6 +76,6 @@ public class Lwm2mController extends BaseController {
public Device saveDeviceWithCredentials(@RequestBody Map<Class<?>, Object> deviceWithDeviceCredentials) throws ThingsboardException {
Device device = checkNotNull(JacksonUtil.convertValue(deviceWithDeviceCredentials.get(Device.class), Device.class));
DeviceCredentials credentials = checkNotNull(JacksonUtil.convertValue(deviceWithDeviceCredentials.get(DeviceCredentials.class), DeviceCredentials.class));
return deviceController.saveDeviceWithCredentials(new SaveDeviceWithCredentialsRequest(device, credentials));
return deviceController.saveDeviceWithCredentials(new SaveDeviceWithCredentialsRequest(device, credentials), DEFAULT.policy(), DEFAULT.separator(), DEFAULT.uniquifyStrategy());
}
}

8
application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java

@ -20,6 +20,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.audit.ActionType;
@ -40,10 +41,15 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb
@Override
public Asset save(Asset asset, User user) throws Exception {
return save(asset, NameConflictStrategy.DEFAULT, user);
}
@Override
public Asset save(Asset asset, NameConflictStrategy nameConflictStrategy, User user) throws Exception {
ActionType actionType = asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = asset.getTenantId();
try {
Asset savedAsset = checkNotNull(assetService.saveAsset(asset));
Asset savedAsset = checkNotNull(assetService.saveAsset(asset, nameConflictStrategy));
autoCommit(user, savedAsset.getId());
logEntityActionService.logEntityAction(tenantId, savedAsset.getId(), savedAsset, asset.getCustomerId(),
actionType, user);

3
application/src/main/java/org/thingsboard/server/service/entitiy/asset/TbAssetService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.service.entitiy.asset;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.edge.Edge;
@ -27,6 +28,8 @@ public interface TbAssetService {
Asset save(Asset asset, User user) throws Exception;
Asset save(Asset asset, NameConflictStrategy nameConflictStrategy, User user) throws Exception;
void delete(Asset asset, User user);
Asset assignAssetToCustomer(TenantId tenantId, AssetId assetId, Customer customer, User user) throws ThingsboardException;

8
application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java

@ -19,6 +19,7 @@ import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.CustomerId;
@ -32,10 +33,15 @@ public class DefaultTbCustomerService extends AbstractTbEntityService implements
@Override
public Customer save(Customer customer, SecurityUser user) throws Exception {
return save(customer, NameConflictStrategy.DEFAULT, user);
}
@Override
public Customer save(Customer customer, NameConflictStrategy nameConflictStrategy, SecurityUser user) throws Exception {
ActionType actionType = customer.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = customer.getTenantId();
try {
Customer savedCustomer = checkNotNull(customerService.saveCustomer(customer));
Customer savedCustomer = checkNotNull(customerService.saveCustomer(customer, nameConflictStrategy));
autoCommit(user, savedCustomer.getId());
logEntityActionService.logEntityAction(tenantId, savedCustomer.getId(), savedCustomer, null, actionType, user);
return savedCustomer;

4
application/src/main/java/org/thingsboard/server/service/entitiy/customer/TbCustomerService.java

@ -16,8 +16,12 @@
package org.thingsboard.server.service.entitiy.customer;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.service.entitiy.SimpleTbEntityService;
import org.thingsboard.server.service.security.model.SecurityUser;
public interface TbCustomerService extends SimpleTbEntityService<Customer> {
Customer save(Customer customer, NameConflictStrategy nameConflictStrategy, SecurityUser user) throws Exception;
}

15
application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java

@ -25,6 +25,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
@ -56,10 +57,15 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T
@Override
public Device save(Device device, String accessToken, User user) throws Exception {
return save(device, accessToken, NameConflictStrategy.DEFAULT, user);
}
@Override
public Device save(Device device, String accessToken, NameConflictStrategy nameConflictStrategy, User user) throws Exception {
ActionType actionType = device.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = device.getTenantId();
try {
Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken));
Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken, nameConflictStrategy));
autoCommit(user, savedDevice.getId());
logEntityActionService.logEntityAction(tenantId, savedDevice.getId(), savedDevice, savedDevice.getCustomerId(),
actionType, user);
@ -73,10 +79,15 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T
@Override
public Device saveDeviceWithCredentials(Device device, DeviceCredentials credentials, User user) throws ThingsboardException {
return saveDeviceWithCredentials(device, credentials, NameConflictStrategy.DEFAULT, user);
}
@Override
public Device saveDeviceWithCredentials(Device device, DeviceCredentials credentials, NameConflictStrategy nameConflictStrategy, User user) throws ThingsboardException {
ActionType actionType = device.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = device.getTenantId();
try {
Device savedDevice = checkNotNull(deviceService.saveDeviceWithCredentials(device, credentials));
Device savedDevice = checkNotNull(deviceService.saveDeviceWithCredentials(device, credentials, nameConflictStrategy));
logEntityActionService.logEntityAction(tenantId, savedDevice.getId(), savedDevice, savedDevice.getCustomerId(),
actionType, user);

5
application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.entitiy.device;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.edge.Edge;
@ -33,8 +34,12 @@ public interface TbDeviceService {
Device save(Device device, String accessToken, User user) throws Exception;
Device save(Device device, String accessToken, NameConflictStrategy nameConflictStrategy, User user) throws Exception;
Device saveDeviceWithCredentials(Device device, DeviceCredentials deviceCredentials, User user) throws ThingsboardException;
Device saveDeviceWithCredentials(Device device, DeviceCredentials deviceCredentials, NameConflictStrategy nameConflictStrategy, User user) throws ThingsboardException;
void delete(Device device, User user);
Device assignDeviceToCustomer(TenantId tenantId, DeviceId deviceId, Customer customer, User user) throws ThingsboardException;

8
application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java

@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
@ -80,10 +81,15 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
@Override
public EntityView save(EntityView entityView, EntityView existingEntityView, User user) throws Exception {
return save(entityView, existingEntityView, NameConflictStrategy.DEFAULT, user);
}
@Override
public EntityView save(EntityView entityView, EntityView existingEntityView, NameConflictStrategy nameConflictStrategy, User user) throws Exception {
ActionType actionType = entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = entityView.getTenantId();
try {
EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView));
EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView, nameConflictStrategy));
this.updateEntityViewAttributes(tenantId, savedEntityView, existingEntityView, user);
autoCommit(user, savedEntityView.getId());
logEntityActionService.logEntityAction(savedEntityView.getTenantId(), savedEntityView.getId(), savedEntityView,

3
application/src/main/java/org/thingsboard/server/service/entitiy/entityview/TbEntityViewService.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.entitiy.entityview;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.exception.ThingsboardException;
@ -33,6 +34,8 @@ public interface TbEntityViewService extends ComponentLifecycleListener {
EntityView save(EntityView entityView, EntityView existingEntityView, User user) throws Exception;
EntityView save(EntityView entityView, EntityView existingEntityView, NameConflictStrategy nameConflictStrategy, User user) throws Exception;
void updateEntityViewAttributes(TenantId tenantId, EntityView savedEntityView, EntityView oldEntityView, User user) throws ThingsboardException;
void delete(EntityView entity, User user) throws ThingsboardException;

24
application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java

@ -1080,6 +1080,30 @@ public class AssetControllerTest extends AbstractControllerTest {
testEntityDaoWithRelationsTransactionalException(assetDao, savedTenant.getId(), assetId, "/api/asset/" + assetId);
}
@Test
public void testSaveAssetWithUniquifyStrategy() throws Exception {
Asset asset = new Asset();
asset.setName("My unique asset");
asset.setType("default");
doPost("/api/asset", asset, Asset.class);
doPost("/api/asset", asset).andExpect(status().isBadRequest());
doPost("/api/asset?nameConflictPolicy=FAIL", asset).andExpect(status().isBadRequest());
Asset secondAsset = doPost("/api/asset?nameConflictPolicy=UNIQUIFY", asset, Asset.class);
assertThat(secondAsset.getName()).startsWith("My unique asset_");
Asset thirdAsset = doPost("/api/asset?nameConflictPolicy=UNIQUIFY&uniquifySeparator=-", asset, Asset.class);
assertThat(thirdAsset.getName()).startsWith("My unique asset-");
Asset fourthAsset = doPost("/api/asset?nameConflictPolicy=UNIQUIFY&uniquifyStrategy=INCREMENTAL", asset, Asset.class);
assertThat(fourthAsset.getName()).isEqualTo("My unique asset_1");
Asset fifthAsset = doPost("/api/asset?nameConflictPolicy=UNIQUIFY&uniquifyStrategy=INCREMENTAL", asset, Asset.class);
assertThat(fifthAsset.getName()).isEqualTo("My unique asset_2");
}
private Asset createAsset(String name) {
Asset asset = new Asset();
asset.setName(name);

22
application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java

@ -33,6 +33,7 @@ import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
@ -462,6 +463,27 @@ public class CustomerControllerTest extends AbstractControllerTest {
testEntityDaoWithRelationsTransactionalException(customerDao, savedTenant.getId(), customerId, "/api/customer/" + customerId);
}
@Test
public void testSaveCustomerWithUniquifyStrategy() throws Exception {
Customer customer = new Customer();
customer.setTitle("My unique customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
doPost("/api/customer?nameConflictPolicy=FAIL", customer).andExpect(status().isBadRequest());
Customer secondCustomer = doPost("/api/customer?nameConflictPolicy=UNIQUIFY", customer, Customer.class);
assertThat(secondCustomer.getName()).startsWith("My unique customer_");
Customer thirdCustomer = doPost("/api/customer?nameConflictPolicy=UNIQUIFY&uniquifySeparator=-", customer, Customer.class);
assertThat(thirdCustomer.getName()).startsWith("My unique customer-");
Customer fourthCustomer = doPost("/api/customer?nameConflictPolicy=UNIQUIFY&uniquifyStrategy=INCREMENTAL", customer, Customer.class);
assertThat(fourthCustomer.getName()).isEqualTo("My unique customer_1");
Customer fifthCustomer = doPost("/api/customer?nameConflictPolicy=UNIQUIFY&uniquifyStrategy=INCREMENTAL", customer, Customer.class);
assertThat(fifthCustomer.getName()).isEqualTo("My unique customer_2");
}
private Customer createCustomer(String title) {
Customer customer = new Customer();
customer.setTitle(title);

24
application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java

@ -1608,6 +1608,30 @@ public class DeviceControllerTest extends AbstractControllerTest {
assertThat(device.getVersion()).isEqualTo(3);
}
@Test
public void testSaveDeviceWithUniquifyStrategy() throws Exception {
Device device = new Device();
device.setName("My unique device");
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
doPost("/api/device", device).andExpect(status().isBadRequest());
doPost("/api/device?nameConflictPolicy=FAIL", device).andExpect(status().isBadRequest());
Device secondDevice = doPost("/api/device?nameConflictPolicy=UNIQUIFY", device, Device.class);
assertThat(secondDevice.getName()).startsWith("My unique device_");
Device thirdDevice = doPost("/api/device?nameConflictPolicy=UNIQUIFY&uniquifySeparator=-", device, Device.class);
assertThat(thirdDevice.getName()).startsWith("My unique device-");
Device fourthDevice = doPost("/api/device?nameConflictPolicy=UNIQUIFY&uniquifyStrategy=INCREMENTAL", device, Device.class);
assertThat(fourthDevice.getName()).isEqualTo("My unique device_1");
Device fifthDevice = doPost("/api/device?nameConflictPolicy=UNIQUIFY&uniquifyStrategy=INCREMENTAL", device, Device.class);
assertThat(fifthDevice.getName()).isEqualTo("My unique device_2");
}
private Device createDevice(String name) {
Device device = new Device();
device.setName(name);

25
application/src/test/java/org/thingsboard/server/controller/EntityViewControllerTest.java

@ -853,4 +853,29 @@ public class EntityViewControllerTest extends AbstractControllerTest {
EntityViewId entityViewId = getNewSavedEntityView("EntityView for Test WithRelations Transactional Exception").getId();
testEntityDaoWithRelationsTransactionalException(entityViewDao, tenantId, entityViewId, "/api/entityView/" + entityViewId);
}
@Test
public void testSaveEntityViewWithUniquifyStrategy() throws Exception {
EntityView view = new EntityView();
view.setEntityId(testDevice.getId());
view.setTenantId(tenantId);
view.setType("default");
view.setName("My unique view");
EntityView savedView = doPost("/api/entityView", view, EntityView.class);
doPost("/api/entityView?nameConflictPolicy=FAIL", view).andExpect(status().isBadRequest());
EntityView secondView = doPost("/api/entityView?nameConflictPolicy=UNIQUIFY", view, EntityView.class);
assertThat(secondView.getName()).startsWith("My unique view_");
EntityView thirdView = doPost("/api/entityView?nameConflictPolicy=UNIQUIFY&uniquifySeparator=-", view, EntityView.class);
assertThat(thirdView.getName()).startsWith("My unique view-");
EntityView fourthView = doPost("/api/entityView?nameConflictPolicy=UNIQUIFY&uniquifyStrategy=INCREMENTAL", view, EntityView.class);
assertThat(fourthView.getName()).isEqualTo("My unique view_1");
EntityView fifthEntityView = doPost("/api/entityView?nameConflictPolicy=UNIQUIFY&uniquifyStrategy=INCREMENTAL", view, EntityView.class);
assertThat(fifthEntityView.getName()).isEqualTo("My unique view_2");
}
}

3
common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.asset;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.ProfileEntityIdInfo;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo;
@ -48,6 +49,8 @@ public interface AssetService extends EntityDaoService {
Asset saveAsset(Asset asset);
Asset saveAsset(Asset asset, NameConflictStrategy nameConflictStrategy);
Asset assignAssetToCustomer(TenantId tenantId, AssetId assetId, CustomerId customerId);
Asset unassignAssetFromCustomer(TenantId tenantId, AssetId assetId);

3
common/dao-api/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.customer;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@ -37,6 +38,8 @@ public interface CustomerService extends EntityDaoService {
Customer saveCustomer(Customer customer);
Customer saveCustomer(Customer customer, NameConflictStrategy nameConflictStrategy);
void deleteCustomer(TenantId tenantId, CustomerId customerId);
Customer findOrCreatePublicCustomer(TenantId tenantId);

5
common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.DeviceInfoFilter;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.ProfileEntityIdInfo;
import org.thingsboard.server.common.data.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.id.CustomerId;
@ -58,8 +59,12 @@ public interface DeviceService extends EntityDaoService {
Device saveDeviceWithAccessToken(Device device, String accessToken);
Device saveDeviceWithAccessToken(Device device, String accessToken, NameConflictStrategy nameConflictStrategy);
Device saveDeviceWithCredentials(Device device, DeviceCredentials deviceCredentials);
Device saveDeviceWithCredentials(Device device, DeviceCredentials deviceCredentials, NameConflictStrategy nameConflictStrategy);
Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile);
Device assignDeviceToCustomer(TenantId tenantId, DeviceId deviceId, CustomerId customerId);

3
common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java

@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EdgeId;
@ -38,6 +39,8 @@ public interface EntityViewService extends EntityDaoService {
EntityView saveEntityView(EntityView entityView);
EntityView saveEntityView(EntityView entityView, NameConflictStrategy nameConflictStrategy);
EntityView saveEntityView(EntityView entityView, boolean doValidate);
EntityView assignEntityViewToCustomer(TenantId tenantId, EntityViewId entityViewId, CustomerId customerId);

23
common/data/src/main/java/org/thingsboard/server/common/data/NameConflictPolicy.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2025 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;
public enum NameConflictPolicy {
FAIL,
UNIQUIFY;
}

25
common/data/src/main/java/org/thingsboard/server/common/data/NameConflictStrategy.java

@ -0,0 +1,25 @@
/**
* Copyright © 2016-2025 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;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema
public record NameConflictStrategy(NameConflictPolicy policy, String separator, UniquifyStrategy uniquifyStrategy) {
public static final NameConflictStrategy DEFAULT = new NameConflictStrategy(NameConflictPolicy.FAIL, null, null);
}

23
common/data/src/main/java/org/thingsboard/server/common/data/UniquifyStrategy.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2025 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;
public enum UniquifyStrategy {
RANDOM,
INCREMENTAL;
}

5
dao/src/main/java/org/thingsboard/server/dao/Dao.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.edqs.fields.EntityFields;
import org.thingsboard.server.common.data.id.TenantId;
@ -32,6 +33,10 @@ public interface Dao<T> {
ListenableFuture<T> findByIdAsync(TenantId tenantId, UUID id);
default List<EntityInfo> findEntityInfosByNamePrefix(TenantId tenantId, String name) {
throw new UnsupportedOperationException();
}
boolean existsById(TenantId tenantId, UUID id);
ListenableFuture<Boolean> existsByIdAsync(TenantId tenantId, UUID id);

20
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -26,6 +26,8 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.NameConflictPolicy;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.ProfileEntityIdInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.asset.Asset;
@ -146,14 +148,24 @@ public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey,
return saveAsset(asset, true);
}
@Override
public Asset saveAsset(Asset asset, NameConflictStrategy nameConflictStrategy) {
return saveAsset(asset, true, nameConflictStrategy);
}
@Override
public Asset saveAsset(Asset asset, boolean doValidate) {
return saveAsset(asset, doValidate, NameConflictStrategy.DEFAULT);
}
private Asset saveAsset(Asset asset, boolean doValidate, NameConflictStrategy nameConflictStrategy) {
log.trace("Executing saveAsset [{}]", asset);
Asset oldAsset = null;
Asset oldAsset = (asset.getId() != null) ? findAssetById(asset.getTenantId(), asset.getId()) : null;
if (nameConflictStrategy.policy() == NameConflictPolicy.UNIQUIFY && (oldAsset == null || !oldAsset.getName().equals(asset.getName()))) {
uniquifyEntityName(asset, oldAsset, asset::setName, EntityType.ASSET, nameConflictStrategy);
}
if (doValidate) {
oldAsset = assetValidator.validate(asset, Asset::getTenantId);
} else if (asset.getId() != null) {
oldAsset = findAssetById(asset.getTenantId(), asset.getId());
assetValidator.validate(asset, Asset::getTenantId);
}
AssetCacheEvictEvent evictEvent = new AssetCacheEvictEvent(asset.getTenantId(), asset.getName(), oldAsset != null ? oldAsset.getName() : null);
Asset savedAsset;

26
dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java

@ -28,6 +28,8 @@ import org.thingsboard.server.cache.customer.CustomerCacheEvictEvent;
import org.thingsboard.server.cache.customer.CustomerCacheKey;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.NameConflictPolicy;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
@ -139,19 +141,29 @@ public class CustomerServiceImpl extends AbstractCachedEntityService<CustomerCac
@Override
@Transactional
public Customer saveCustomer(Customer customer) {
return saveCustomer(customer, true);
return saveCustomer(customer, true, NameConflictStrategy.DEFAULT);
}
@Override
@Transactional
public Customer saveCustomer(Customer customer, NameConflictStrategy nameConflictStrategy) {
return saveCustomer(customer, true, nameConflictStrategy);
}
private Customer saveCustomer(Customer customer, boolean doValidate) {
return saveCustomer(customer, doValidate, NameConflictStrategy.DEFAULT);
}
private Customer saveCustomer(Customer customer, boolean doValidate, NameConflictStrategy nameConflictStrategy) {
log.trace("Executing saveCustomer [{}]", customer);
String oldCustomerTitle = null;
Customer oldCustomer = (customer.getId() != null) ? findCustomerById(customer.getTenantId(), customer.getId()) : null;
if (nameConflictStrategy.policy() == NameConflictPolicy.UNIQUIFY && (oldCustomer == null || !oldCustomer.getTitle().equals(customer.getTitle()))) {
uniquifyEntityName(customer, oldCustomer, customer::setTitle, EntityType.CUSTOMER, nameConflictStrategy);
}
if (doValidate) {
Customer oldCustomer = customerValidator.validate(customer, Customer::getTenantId);
if (oldCustomer != null) {
oldCustomerTitle = oldCustomer.getTitle();
}
customerValidator.validate(customer, Customer::getTenantId);
}
var evictEvent = new CustomerCacheEvictEvent(customer.getTenantId(), customer.getTitle(), oldCustomerTitle);
var evictEvent = new CustomerCacheEvictEvent(customer.getTenantId(), customer.getTitle(), oldCustomer != null ? oldCustomer.getTitle() : null);
try {
Customer savedCustomer = customerDao.saveAndFlush(customer.getTenantId(), customer);
if (!savedCustomer.isPublic()) {

33
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -39,6 +39,8 @@ import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.NameConflictPolicy;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.ProfileEntityIdInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
@ -167,6 +169,12 @@ public class DeviceServiceImpl extends CachedVersionedEntityService<DeviceCacheK
return doSaveDevice(device, accessToken, true);
}
@Transactional
@Override
public Device saveDeviceWithAccessToken(Device device, String accessToken, NameConflictStrategy nameConflictStrategy) {
return doSaveDevice(device, accessToken, true, nameConflictStrategy);
}
@Override
public Device saveDevice(Device device, boolean doValidate) {
return doSaveDevice(device, null, doValidate);
@ -181,7 +189,13 @@ public class DeviceServiceImpl extends CachedVersionedEntityService<DeviceCacheK
@Transactional
@Override
public Device saveDeviceWithCredentials(Device device, DeviceCredentials deviceCredentials) {
Device savedDevice = this.saveDeviceWithoutCredentials(device, true);
return this.saveDeviceWithCredentials(device, deviceCredentials, NameConflictStrategy.DEFAULT);
}
@Transactional
@Override
public Device saveDeviceWithCredentials(Device device, DeviceCredentials deviceCredentials, NameConflictStrategy nameConflictStrategy) {
Device savedDevice = this.saveDeviceWithoutCredentials(device, true, nameConflictStrategy);
deviceCredentials.setDeviceId(savedDevice.getId());
if (device.getId() == null) {
deviceCredentialsService.createDeviceCredentials(savedDevice.getTenantId(), deviceCredentials);
@ -198,7 +212,11 @@ public class DeviceServiceImpl extends CachedVersionedEntityService<DeviceCacheK
}
private Device doSaveDevice(Device device, String accessToken, boolean doValidate) {
Device savedDevice = this.saveDeviceWithoutCredentials(device, doValidate);
return doSaveDevice(device, accessToken, doValidate, NameConflictStrategy.DEFAULT);
}
private Device doSaveDevice(Device device, String accessToken, boolean doValidate, NameConflictStrategy nameConflictStrategy) {
Device savedDevice = this.saveDeviceWithoutCredentials(device, doValidate, nameConflictStrategy);
if (device.getId() == null) {
DeviceCredentials deviceCredentials = new DeviceCredentials();
deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId()));
@ -209,13 +227,14 @@ public class DeviceServiceImpl extends CachedVersionedEntityService<DeviceCacheK
return savedDevice;
}
private Device saveDeviceWithoutCredentials(Device device, boolean doValidate) {
private Device saveDeviceWithoutCredentials(Device device, boolean doValidate, NameConflictStrategy nameConflictStrategy) {
log.trace("Executing saveDevice [{}]", device);
Device oldDevice = null;
Device oldDevice = (device.getId() != null) ? findDeviceById(device.getTenantId(), device.getId()) : null;
if (nameConflictStrategy.policy() == NameConflictPolicy.UNIQUIFY && (oldDevice == null || !oldDevice.getName().equals(device.getName()))) {
uniquifyEntityName(device, oldDevice, device::setName, EntityType.DEVICE, nameConflictStrategy);
}
if (doValidate) {
oldDevice = deviceValidator.validate(device, Device::getTenantId);
} else if (device.getId() != null) {
oldDevice = findDeviceById(device.getTenantId(), device.getId());
deviceValidator.validate(device, Device::getTenantId);
}
DeviceCacheEvictEvent deviceCacheEvictEvent = new DeviceCacheEvictEvent(device.getTenantId(), device.getId(), device.getName(), oldDevice != null ? oldDevice.getName() : null);
try {

44
dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java

@ -22,15 +22,22 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Lazy;
import org.thingsboard.common.util.DebugModeUtil;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.HasDebugSettings;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.debug.DebugSettings;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.edge.EdgeService;
@ -44,7 +51,12 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.UniquifyStrategy.RANDOM;
@Slf4j
public abstract class AbstractEntityService {
@ -83,6 +95,9 @@ public abstract class AbstractEntityService {
@Lazy
protected TbTenantProfileCache tbTenantProfileCache;
@Autowired
protected EntityDaoRegistry entityDaoRegistry;
@Value("${debug.settings.default_duration:15}")
private int defaultDebugDurationMinutes;
@ -155,4 +170,33 @@ public abstract class AbstractEntityService {
private long getMaxDebugAllUntil(TenantId tenantId, long now) {
return now + TimeUnit.MINUTES.toMillis(DebugModeUtil.getMaxDebugAllDuration(tbTenantProfileCache.get(tenantId).getDefaultProfileConfiguration().getMaxDebugModeDurationMinutes(), defaultDebugDurationMinutes));
}
protected <E extends HasId<?> & HasTenantId & HasName> void uniquifyEntityName(E entity, E oldEntity, Consumer<String> setName, EntityType entityType, NameConflictStrategy strategy) {
Dao<?> dao = entityDaoRegistry.getDao(entityType);
List<EntityInfo> existingEntities = dao.findEntityInfosByNamePrefix(entity.getTenantId(), entity.getName());
Set<String> existingNames = existingEntities.stream()
.filter(e -> (oldEntity == null || !e.getId().equals(oldEntity.getId())))
.map(EntityInfo::getName)
.collect(Collectors.toSet());
if (existingNames.contains(entity.getName())) {
String uniqueName = generateUniqueName(entity.getName(), existingNames, strategy);
setName.accept(uniqueName);
}
}
private String generateUniqueName(String baseName, Set<String> existingNames, NameConflictStrategy strategy) {
String newName;
int index = 1;
String separator = strategy.separator();
boolean isRandom = strategy.uniquifyStrategy() == RANDOM;
do {
String suffix = isRandom ? StringUtils.randomAlphanumeric(6) : String.valueOf(index++);
newName = baseName + separator + suffix;
} while (existingNames.contains(newName));
return newName;
}
}

20
dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java

@ -29,6 +29,8 @@ import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo;
import org.thingsboard.server.common.data.NameConflictPolicy;
import org.thingsboard.server.common.data.NameConflictStrategy;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
@ -110,14 +112,24 @@ public class EntityViewServiceImpl extends CachedVersionedEntityService<EntityVi
return saveEntityView(entityView, true);
}
@Override
public EntityView saveEntityView(EntityView entityView, NameConflictStrategy nameConflictStrategy) {
return saveEntityView(entityView, true, nameConflictStrategy);
}
@Override
public EntityView saveEntityView(EntityView entityView, boolean doValidate) {
return saveEntityView(entityView, doValidate, NameConflictStrategy.DEFAULT);
}
private EntityView saveEntityView(EntityView entityView, boolean doValidate, NameConflictStrategy nameConflictStrategy) {
log.trace("Executing save entity view [{}]", entityView);
EntityView old = null;
EntityView old = (entityView.getId() != null) ? findEntityViewById(entityView.getTenantId(), entityView.getId(), false) : null;
if (nameConflictStrategy.policy() == NameConflictPolicy.UNIQUIFY && (old == null || !entityView.getName().equals(old.getName()))) {
uniquifyEntityName(entityView, old, entityView::setName, EntityType.ENTITY_VIEW, nameConflictStrategy);
}
if (doValidate) {
old = entityViewValidator.validate(entityView, EntityView::getTenantId);
} else if (entityView.getId() != null) {
old = findEntityViewById(entityView.getTenantId(), entityView.getId(), false);
entityViewValidator.validate(entityView, EntityView::getTenantId);
}
try {
EntityView saved = entityViewDao.save(entityView.getTenantId(), entityView);

5
dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java

@ -21,6 +21,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.edqs.fields.AssetFields;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.dao.ExportableEntityRepository;
@ -103,6 +104,10 @@ public interface AssetRepository extends JpaRepository<AssetEntity, UUID>, Expor
AssetEntity findByTenantIdAndName(UUID tenantId, String name);
@Query("SELECT new org.thingsboard.server.common.data.EntityInfo(a.id, 'ASSET', a.name) " +
"FROM AssetEntity a WHERE a.tenantId = :tenantId AND a.name LIKE CONCAT(:prefix, '%')")
List<EntityInfo> findEntityInfosByNamePrefix(UUID tenantId, String prefix);
@Query("SELECT a FROM AssetEntity a WHERE a.tenantId = :tenantId " +
"AND a.type = :type " +
"AND (:textSearch IS NULL OR ilike(a.name, CONCAT('%', :textSearch, '%')) = true " +

7
dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java

@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Limit;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ProfileEntityIdInfo;
@ -267,6 +268,12 @@ public class JpaAssetDao extends JpaAbstractDao<AssetEntity, Asset> implements A
return nativeAssetRepository.findProfileEntityIdInfosByTenantId(tenantId, DaoUtil.toPageable(pageLink));
}
@Override
public List<EntityInfo> findEntityInfosByNamePrefix(TenantId tenantId, String name) {
log.debug("Find asset entity infos by name [{}]", name);
return assetRepository.findEntityInfosByNamePrefix(tenantId.getId(), name);
}
@Override
public Long countByTenantId(TenantId tenantId) {
return assetRepository.countByTenantId(tenantId.getId());

5
dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java

@ -21,6 +21,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.edqs.fields.CustomerFields;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.CustomerEntity;
@ -41,6 +42,10 @@ public interface CustomerRepository extends JpaRepository<CustomerEntity, UUID>,
CustomerEntity findByTenantIdAndTitle(UUID tenantId, String title);
@Query("SELECT new org.thingsboard.server.common.data.EntityInfo(a.id, 'CUSTOMER', a.title) " +
"FROM CustomerEntity a WHERE a.tenantId = :tenantId AND a.title LIKE CONCAT(:prefix, '%')")
List<EntityInfo> findEntityInfosByNamePrefix(UUID tenantId, String prefix);
@Query(value = "SELECT * FROM customer c WHERE c.tenant_id = :tenantId " +
"AND c.is_public IS TRUE ORDER BY c.id ASC LIMIT 1", nativeQuery = true)
CustomerEntity findPublicCustomerByTenantId(@Param("tenantId") UUID tenantId);

6
dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java

@ -20,6 +20,7 @@ import org.springframework.data.domain.Limit;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.edqs.fields.CustomerFields;
import org.thingsboard.server.common.data.id.CustomerId;
@ -117,6 +118,11 @@ public class JpaCustomerDao extends JpaAbstractDao<CustomerEntity, Customer> imp
return customerRepository.findNextBatch(id, Limit.of(batchSize));
}
@Override
public List<EntityInfo> findEntityInfosByNamePrefix(TenantId tenantId, String name) {
return customerRepository.findEntityInfosByNamePrefix(tenantId.getId(), name);
}
@Override
public EntityType getEntityType() {
return EntityType.CUSTOMER;

5
dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java

@ -22,6 +22,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.edqs.fields.DeviceFields;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.DeviceEntity;
@ -151,6 +152,10 @@ public interface DeviceRepository extends JpaRepository<DeviceEntity, UUID>, Exp
DeviceEntity findByTenantIdAndName(UUID tenantId, String name);
@Query("SELECT new org.thingsboard.server.common.data.EntityInfo(a.id, 'DEVICE', a.name) " +
"FROM DeviceEntity a WHERE a.tenantId = :tenantId AND a.name LIKE CONCAT(:prefix, '%')")
List<EntityInfo> findEntityInfosByNamePrefix(UUID tenantId, String prefix);
List<DeviceEntity> findDevicesByTenantIdAndCustomerIdAndIdIn(UUID tenantId, UUID customerId, List<UUID> deviceIds);
List<DeviceEntity> findDevicesByTenantIdAndIdIn(UUID tenantId, List<UUID> deviceIds);

6
dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.DeviceIdInfo;
import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.DeviceInfoFilter;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ProfileEntityIdInfo;
@ -114,6 +115,11 @@ public class JpaDeviceDao extends JpaAbstractDao<DeviceEntity, Device> implement
DaoUtil.toPageable(pageLink)));
}
@Override
public List<EntityInfo> findEntityInfosByNamePrefix(TenantId tenantId, String name) {
return deviceRepository.findEntityInfosByNamePrefix(tenantId.getId(), name);
}
@Override
public ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(UUID tenantId, List<UUID> deviceIds) {
return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findDevicesByTenantIdAndIdIn(tenantId, deviceIds)));

5
dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java

@ -21,6 +21,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.edqs.fields.EntityViewFields;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.EntityViewEntity;
@ -118,6 +119,10 @@ public interface EntityViewRepository extends JpaRepository<EntityViewEntity, UU
EntityViewEntity findByTenantIdAndName(UUID tenantId, String name);
@Query("SELECT new org.thingsboard.server.common.data.EntityInfo(a.id, 'ENTITY_VIEW', a.name) " +
"FROM EntityViewEntity a WHERE a.tenantId = :tenantId AND a.name LIKE CONCAT(:prefix, '%')")
List<EntityInfo> findEntityInfosByNamePrefix(UUID tenantId, String prefix);
List<EntityViewEntity> findAllByTenantIdAndEntityId(UUID tenantId, UUID entityId);
boolean existsByTenantIdAndEntityId(UUID tenantId, UUID entityId);

6
dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java

@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Limit;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
@ -230,6 +231,11 @@ public class JpaEntityViewDao extends JpaAbstractDao<EntityViewEntity, EntityVie
return entityViewRepository.findNextBatch(id, Limit.of(batchSize));
}
@Override
public List<EntityInfo> findEntityInfosByNamePrefix(TenantId tenantId, String name) {
return entityViewRepository.findEntityInfosByNamePrefix(tenantId.getId(), name);
}
@Override
public EntityType getEntityType() {
return EntityType.ENTITY_VIEW;

9
ui-ngx/src/app/core/http/asset.service.ts

@ -15,7 +15,7 @@
///
import { Injectable } from '@angular/core';
import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { createDefaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link';
@ -23,6 +23,7 @@ import { PageData } from '@shared/models/page/page-data';
import { EntitySubtype } from '@shared/models/entity-type.models';
import { Asset, AssetInfo, AssetSearchQuery } from '@shared/models/asset.models';
import { BulkImportRequest, BulkImportResult } from '@shared/import-export/import-export.models';
import { SaveEntityParams } from '@shared/models/entity.models';
@Injectable({
providedIn: 'root'
@ -69,8 +70,10 @@ export class AssetService {
return this.http.get<AssetInfo>(`/api/asset/info/${assetId}`, defaultHttpOptionsFromConfig(config));
}
public saveAsset(asset: Asset, config?: RequestConfig): Observable<Asset> {
return this.http.post<Asset>('/api/asset', asset, defaultHttpOptionsFromConfig(config));
public saveAsset(asset: Asset, config?: RequestConfig): Observable<Asset>;
public saveAsset(asset: Asset, saveParams: SaveEntityParams, config?: RequestConfig): Observable<Asset>;
public saveAsset(asset: Asset, saveParamsOrConfig?: SaveEntityParams | RequestConfig, config?: RequestConfig): Observable<Asset> {
return this.http.post<Asset>('/api/asset', asset, createDefaultHttpOptions(saveParamsOrConfig, config));
}
public deleteAsset(assetId: string, config?: RequestConfig) {

9
ui-ngx/src/app/core/http/customer.service.ts

@ -15,12 +15,13 @@
///
import { Injectable } from '@angular/core';
import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { createDefaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data';
import { Customer } from '@shared/models/customer.model';
import { SaveEntityParams } from '@shared/models/entity.models';
@Injectable({
providedIn: 'root'
@ -40,8 +41,10 @@ export class CustomerService {
return this.http.get<Customer>(`/api/customer/${customerId}`, defaultHttpOptionsFromConfig(config));
}
public saveCustomer(customer: Customer, config?: RequestConfig): Observable<Customer> {
return this.http.post<Customer>('/api/customer', customer, defaultHttpOptionsFromConfig(config));
public saveCustomer(customer: Customer, config?: RequestConfig): Observable<Customer>;
public saveCustomer(customer: Customer, saveParams: SaveEntityParams, config?: RequestConfig): Observable<Customer>;
public saveCustomer(customer: Customer, saveParamsOrConfig?: SaveEntityParams | RequestConfig, config?: RequestConfig): Observable<Customer> {
return this.http.post<Customer>('/api/customer', customer, createDefaultHttpOptions(saveParamsOrConfig, config));
}
public deleteCustomer(customerId: string, config?: RequestConfig) {

18
ui-ngx/src/app/core/http/device.service.ts

@ -15,7 +15,7 @@
///
import { Injectable } from '@angular/core';
import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { createDefaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable, ReplaySubject } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link';
@ -28,13 +28,15 @@ import {
DeviceInfo,
DeviceInfoQuery,
DeviceSearchQuery,
PublishTelemetryCommand
PublishTelemetryCommand,
SaveDeviceParams
} from '@shared/models/device.models';
import { EntitySubtype } from '@shared/models/entity-type.models';
import { AuthService } from '@core/auth/auth.service';
import { BulkImportRequest, BulkImportResult } from '@shared/import-export/import-export.models';
import { PersistentRpc, RpcStatus } from '@shared/models/rpc.models';
import { ResourcesService } from '@core/services/resources.service';
import { SaveEntityParams } from '@shared/models/entity.models';
@Injectable({
providedIn: 'root'
@ -87,15 +89,19 @@ export class DeviceService {
return this.http.get<DeviceInfo>(`/api/device/info/${deviceId}`, defaultHttpOptionsFromConfig(config));
}
public saveDevice(device: Device, config?: RequestConfig): Observable<Device> {
return this.http.post<Device>('/api/device', device, defaultHttpOptionsFromConfig(config));
public saveDevice(device: Device, config?: RequestConfig): Observable<Device>;
public saveDevice(device: Device, saveParams?: SaveDeviceParams, config?: RequestConfig): Observable<Device>;
public saveDevice(device: Device, saveParamsOrConfig?: SaveDeviceParams | RequestConfig, config?: RequestConfig): Observable<Device> {
return this.http.post<Device>('/api/device', device, createDefaultHttpOptions(saveParamsOrConfig, config));
}
public saveDeviceWithCredentials(device: Device, credentials: DeviceCredentials, config?: RequestConfig): Observable<Device> {
public saveDeviceWithCredentials(device: Device, credentials: DeviceCredentials, config?: RequestConfig): Observable<Device>;
public saveDeviceWithCredentials(device: Device, credentials: DeviceCredentials, saveParams: SaveEntityParams, config?: RequestConfig): Observable<Device>;
public saveDeviceWithCredentials(device: Device, credentials: DeviceCredentials, saveParamsOrConfig?: SaveEntityParams | RequestConfig, config?: RequestConfig): Observable<Device> {
return this.http.post<Device>('/api/device-with-credentials', {
device,
credentials
}, defaultHttpOptionsFromConfig(config));
}, createDefaultHttpOptions(saveParamsOrConfig, config));
}
public deleteDevice(deviceId: string, config?: RequestConfig) {

9
ui-ngx/src/app/core/http/entity-view.service.ts

@ -15,13 +15,14 @@
///
import { Injectable } from '@angular/core';
import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { createDefaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data';
import { EntitySubtype } from '@app/shared/models/entity-type.models';
import { EntityView, EntityViewInfo, EntityViewSearchQuery } from '@app/shared/models/entity-view.models';
import { SaveEntityParams } from '@shared/models/entity.models';
@Injectable({
providedIn: 'root'
@ -51,8 +52,10 @@ export class EntityViewService {
return this.http.get<EntityViewInfo>(`/api/entityView/info/${entityViewId}`, defaultHttpOptionsFromConfig(config));
}
public saveEntityView(entityView: EntityView, config?: RequestConfig): Observable<EntityView> {
return this.http.post<EntityView>('/api/entityView', entityView, defaultHttpOptionsFromConfig(config));
public saveEntityView(entityView: EntityView, config?: RequestConfig): Observable<EntityView>;
public saveEntityView(entityView: EntityView, saveParams: SaveEntityParams, config?: RequestConfig): Observable<EntityView>;
public saveEntityView(entityView: EntityView, saveParamsOrConfig?: SaveEntityParams | RequestConfig, config?: RequestConfig): Observable<EntityView> {
return this.http.post<EntityView>('/api/entityView', entityView, createDefaultHttpOptions(saveParamsOrConfig, config));
}
public deleteEntityView(entityViewId: string, config?: RequestConfig) {

34
ui-ngx/src/app/core/http/http-utils.ts

@ -18,32 +18,56 @@ import { InterceptorHttpParams } from '../interceptors/interceptor-http-params';
import { HttpHeaders } from '@angular/common/http';
import { InterceptorConfig } from '../interceptors/interceptor-config';
export type QueryParams = { [param:string]: any };
export interface RequestConfig {
ignoreLoading?: boolean;
ignoreErrors?: boolean;
resendRequest?: boolean;
queryParams?: QueryParams;
}
export function hasRequestConfig(config?: any): boolean {
if (!config) {
return false;
}
return config.hasOwnProperty('ignoreLoading') || config.hasOwnProperty('ignoreErrors') || config.hasOwnProperty('resendRequest') || config.hasOwnProperty('queryParams');
}
export function createDefaultHttpOptions(queryParamsOrConfig?: QueryParams | RequestConfig, config?: RequestConfig) {
if (hasRequestConfig(queryParamsOrConfig)) {
return defaultHttpOptionsFromConfig(queryParamsOrConfig as RequestConfig);
}
const queryParams = queryParamsOrConfig as QueryParams;
const finalConfig = {
...config,
...(queryParams && { queryParams }),
};
return defaultHttpOptionsFromConfig(finalConfig);
}
export function defaultHttpOptionsFromConfig(config?: RequestConfig) {
if (!config) {
config = {};
}
return defaultHttpOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest);
return defaultHttpOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest, config.queryParams);
}
export function defaultHttpOptions(ignoreLoading: boolean = false,
ignoreErrors: boolean = false,
resendRequest: boolean = false) {
resendRequest: boolean = false,
queryParams?: QueryParams) {
return {
headers: new HttpHeaders({'Content-Type': 'application/json'}),
params: new InterceptorHttpParams(new InterceptorConfig(ignoreLoading, ignoreErrors, resendRequest))
params: new InterceptorHttpParams(new InterceptorConfig(ignoreLoading, ignoreErrors, resendRequest), queryParams)
};
}
export function defaultHttpUploadOptions(ignoreLoading: boolean = false,
ignoreErrors: boolean = false,
resendRequest: boolean = false) {
resendRequest: boolean = false,
queryParams?: QueryParams) {
return {
params: new InterceptorHttpParams(new InterceptorConfig(ignoreLoading, ignoreErrors, resendRequest))
params: new InterceptorHttpParams(new InterceptorConfig(ignoreLoading, ignoreErrors, resendRequest), queryParams)
};
}

2
ui-ngx/src/app/core/interceptors/interceptor-http-params.ts

@ -20,7 +20,7 @@ import { InterceptorConfig } from './interceptor-config';
export class InterceptorHttpParams extends HttpParams {
constructor(
public interceptorConfig: InterceptorConfig,
params?: { [param: string]: string | string[] }
params?: { [param: string]: string | number | boolean | ReadonlyArray<string | number | boolean>; }
) {
super({ fromObject: params });
}

6
ui-ngx/src/app/shared/models/device.models.ts

@ -22,7 +22,7 @@ import { DeviceCredentialsId } from '@shared/models/id/device-credentials-id';
import { EntitySearchQuery } from '@shared/models/relation.models';
import { DeviceProfileId } from '@shared/models/id/device-profile-id';
import { RuleChainId } from '@shared/models/id/rule-chain-id';
import { EntityInfoData, HasTenantId, HasVersion } from '@shared/models/entity.models';
import { EntityInfoData, HasTenantId, HasVersion, SaveEntityParams } from '@shared/models/entity.models';
import { FilterPredicateValue, KeyFilter } from '@shared/models/query/query.models';
import { TimeUnit } from '@shared/models/time/time.models';
import _moment from 'moment';
@ -739,6 +739,10 @@ export interface DeviceInfoFilter {
active?: boolean;
}
export interface SaveDeviceParams extends SaveEntityParams {
accessToken?: string;
}
export class DeviceInfoQuery {
pageLink: PageLink;

16
ui-ngx/src/app/shared/models/entity.models.ts

@ -214,3 +214,19 @@ export interface EntityTestScriptResult {
}
export type VersionedEntity = EntityInfoData & HasVersion | RuleChainMetaData;
export enum NameConflictPolicy {
FAIL = 'FAIL',
UNIQUIFY = 'UNIQUIFY',
}
export enum UniquifyStrategy {
RANDOM = 'RANDOM',
INCREMENTAL = 'INCREMENTAL'
}
export interface SaveEntityParams {
nameConflictPolicy?: NameConflictPolicy;
uniquifyStrategy?: UniquifyStrategy;
uniquifySeparator?: string;
}

Loading…
Cancel
Save