Browse Source

Improve 'Entities Limit Exceeded' Error and Notification Template.

pull/14551/head
Igor Kulikov 9 months ago
parent
commit
551574af3b
  1. 4
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  2. 50
      application/src/main/java/org/thingsboard/server/exception/ThingsboardEntitiesLimitExceededResponse.java
  3. 31
      application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java
  4. 23
      application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java
  5. 4
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  6. 4
      dao/src/main/java/org/thingsboard/server/dao/exception/EntitiesLimitExceededException.java
  7. 25
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java
  8. 4
      dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
  9. 1
      ui-ngx/src/app/shared/models/constants.ts
  10. 3
      ui-ngx/src/assets/locale/locale.constant-en_US.json

4
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -151,7 +151,7 @@ import org.thingsboard.server.dao.domain.DomainService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.EntitiesLimitException;
import org.thingsboard.server.dao.exception.EntitiesLimitExceededException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.job.JobService;
import org.thingsboard.server.dao.mobile.MobileAppBundleService;
@ -453,7 +453,7 @@ public abstract class BaseController {
}
if (exception instanceof ThingsboardException) {
return (ThingsboardException) exception;
} else if (exception instanceof EntitiesLimitException) {
} else if (exception instanceof EntitiesLimitExceededException) {
return new ThingsboardException(exception, ThingsboardErrorCode.ENTITIES_LIMIT_EXCEEDED);
} else if (exception instanceof IllegalArgumentException || exception instanceof IncorrectParameterException
|| exception instanceof DataValidationException || cause instanceof IncorrectParameterException) {

50
application/src/main/java/org/thingsboard/server/exception/ThingsboardEntitiesLimitExceededResponse.java

@ -0,0 +1,50 @@
/**
* 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.exception;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.http.HttpStatus;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
@Schema
public class ThingsboardEntitiesLimitExceededResponse extends ThingsboardErrorResponse {
private final EntityType entityType;
private final Long limit;
protected ThingsboardEntitiesLimitExceededResponse(String message, EntityType entityType, Long limit) {
super(message, ThingsboardErrorCode.ENTITIES_LIMIT_EXCEEDED, HttpStatus.FORBIDDEN);
this.entityType = entityType;
this.limit = limit;
}
public static ThingsboardEntitiesLimitExceededResponse of(final String message, final EntityType entityType, final Long limit) {
return new ThingsboardEntitiesLimitExceededResponse(message, entityType, limit);
}
@Schema(description = "Entity type", accessMode = Schema.AccessMode.READ_ONLY)
public EntityType getEntityType() {
return entityType;
}
@Schema(description = "Limit", accessMode = Schema.AccessMode.READ_ONLY)
public Long getLimit() {
return limit;
}
}

31
application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java

@ -15,10 +15,8 @@
*/
package org.thingsboard.server.exception;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.http.HttpStatus;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
@Schema
@ -34,19 +32,9 @@ public class ThingsboardErrorResponse {
private final long timestamp;
private EntityType entityType;
private Long limit;
protected ThingsboardErrorResponse(final String message, final ThingsboardErrorCode errorCode, HttpStatus status) {
this(message, errorCode, null, null, status);
}
protected ThingsboardErrorResponse(final String message, final ThingsboardErrorCode errorCode, EntityType entityType, Long limit, HttpStatus status) {
this.message = message;
this.errorCode = errorCode;
this.entityType = entityType;
this.limit = limit;
this.status = status;
this.timestamp = System.currentTimeMillis();
}
@ -55,14 +43,6 @@ public class ThingsboardErrorResponse {
return new ThingsboardErrorResponse(message, errorCode, status);
}
public static ThingsboardErrorResponse ofEntityLimitExceeded(final String message,
EntityType entityType,
Long limit,
HttpStatus status) {
return new ThingsboardErrorResponse(message, ThingsboardErrorCode.ENTITIES_LIMIT_EXCEEDED,
entityType, limit, status);
}
@Schema(description = "HTTP Response Status Code", example = "401", accessMode = Schema.AccessMode.READ_ONLY)
public Integer getStatus() {
return status.value();
@ -84,7 +64,8 @@ public class ThingsboardErrorResponse {
"\n\n* `32` - Item not found (HTTP: 404 - Not Found)" +
"\n\n* `33` - Too many requests (HTTP: 429 - Too Many Requests)" +
"\n\n* `34` - Too many updates (Too many updates over Websocket session)" +
"\n\n* `40` - Subscription violation (HTTP: 403 - Forbidden)",
"\n\n* `40` - Subscription violation (HTTP: 403 - Forbidden)" +
"\n\n* `41` - Entities limit exceeded (HTTP: 403 - Forbidden)",
example = "10", type = "integer",
accessMode = Schema.AccessMode.READ_ONLY)
public ThingsboardErrorCode getErrorCode() {
@ -95,12 +76,4 @@ public class ThingsboardErrorResponse {
public long getTimestamp() {
return timestamp;
}
public EntityType getEntityType() {
return entityType;
}
public Long getLimit() {
return limit;
}
}

23
application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java

@ -52,7 +52,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.msg.tools.MaxPayloadSizeExceededException;
import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
import org.thingsboard.server.dao.exception.EntitiesLimitException;
import org.thingsboard.server.dao.exception.EntitiesLimitExceededException;
import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException;
import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
import org.thingsboard.server.service.security.exception.UserPasswordExpiredException;
@ -147,10 +147,10 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand
} else if (thingsboardException.getErrorCode() == ThingsboardErrorCode.DATABASE) {
handleDatabaseException(thingsboardException.getCause(), response);
} else if (thingsboardException.getErrorCode() == ThingsboardErrorCode.ENTITIES_LIMIT_EXCEEDED) {
if (thingsboardException.getCause() instanceof EntitiesLimitException entitiesLimitException) {
handleEntitiesLimitException(entitiesLimitException, response);
if (thingsboardException.getCause() instanceof EntitiesLimitExceededException entitiesLimitExceededException) {
handleEntitiesLimitExceededException(entitiesLimitExceededException, response);
} else {
handleEntitiesLimitException(thingsboardException, response);
handleEntitiesLimitExceededException(thingsboardException, response);
}
} else {
handleThingsboardException(thingsboardException, response);
@ -227,19 +227,18 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand
writeResponse(errorResponse, response);
}
private void handleEntitiesLimitException(ThingsboardException entitiesLimitException, HttpServletResponse response) throws IOException {
private void handleEntitiesLimitExceededException(ThingsboardException entitiesLimitExceededException, HttpServletResponse response) throws IOException {
response.setStatus(HttpStatus.FORBIDDEN.value());
JacksonUtil.writeValue(response.getWriter(),
JacksonUtil.fromBytes(((HttpClientErrorException) entitiesLimitException.getCause()).getResponseBodyAsByteArray(), Object.class));
JacksonUtil.fromBytes(((HttpClientErrorException) entitiesLimitExceededException.getCause()).getResponseBodyAsByteArray(), Object.class));
}
private void handleEntitiesLimitException(EntitiesLimitException entitiesLimitException, HttpServletResponse response) throws IOException {
EntityType entityType = entitiesLimitException.getEntityType();
Long limit = entitiesLimitException.getLimit();
HttpStatus status = HttpStatus.FORBIDDEN;
response.setStatus(status.value());
private void handleEntitiesLimitExceededException(EntitiesLimitExceededException entitiesLimitExceededException, HttpServletResponse response) throws IOException {
EntityType entityType = entitiesLimitExceededException.getEntityType();
Long limit = entitiesLimitExceededException.getLimit();
response.setStatus(HttpStatus.FORBIDDEN.value());
JacksonUtil.writeValue(response.getWriter(),
ThingsboardErrorResponse.ofEntityLimitExceeded(entitiesLimitException.getMessage(), entityType, limit, status));
ThingsboardEntitiesLimitExceededResponse.of(entitiesLimitExceededException.getMessage(), entityType, limit));
}
private void handleAccessDeniedException(HttpServletResponse response) throws IOException {

4
application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java

@ -76,7 +76,7 @@ import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.device.provision.ProvisionResponse;
import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
import org.thingsboard.server.dao.exception.EntitiesLimitException;
import org.thingsboard.server.dao.exception.EntitiesLimitExceededException;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService;
@ -398,7 +398,7 @@ public class DefaultTransportApiService implements TransportApiService {
} catch (JsonProcessingException e) {
log.warn("[{}] Failed to lookup device by gateway id and name: [{}]", gatewayId, requestMsg.getDeviceName(), e);
throw new RuntimeException(e);
} catch (EntitiesLimitException e) {
} catch (EntitiesLimitExceededException e) {
log.warn("[{}][{}] API limit exception: [{}]", e.getTenantId(), gatewayId, e.getMessage());
return TransportApiResponseMsg.newBuilder()
.setGetOrCreateDeviceResponseMsg(

4
dao/src/main/java/org/thingsboard/server/dao/exception/EntitiesLimitException.java → dao/src/main/java/org/thingsboard/server/dao/exception/EntitiesLimitExceededException.java

@ -19,7 +19,7 @@ import lombok.Getter;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
public class EntitiesLimitException extends DataValidationException {
public class EntitiesLimitExceededException extends DataValidationException {
private static final long serialVersionUID = -9211462514373279196L;
@Getter
@ -30,7 +30,7 @@ public class EntitiesLimitException extends DataValidationException {
@Getter
private final long limit;
public EntitiesLimitException(TenantId tenantId, EntityType entityType, long limit) {
public EntitiesLimitExceededException(TenantId tenantId, EntityType entityType, long limit) {
super(entityType.getNormalName() + "s limit reached");
this.tenantId = tenantId;
this.entityType = entityType;

25
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java

@ -113,7 +113,30 @@ public class DefaultNotifications {
.button("${increaseLimitActionLabel}").link("${increaseLimitLink}")
.emailTemplate(DefaultEmailTemplate.builder()
.subject("${entityType} limit increase request")
.body("${userEmail} has reached the maximum number of ${entityType:lowerCase}s allowed and is requesting an increase to the ${entityType:lowerCase} limit.<br/><a href=\"${baseUrl}${increaseLimitLink}\">${increaseLimitActionLabel}</a>")
.body("""
<table style="box-sizing: border-box; border-radius: 3px; width: 100%; background-color: #f6f6f6; margin: 0px auto;" cellspacing="0" cellpadding="0" bgcolor="#f6f6f6">
<tbody>
<tr style="box-sizing: border-box; margin: 0px;">
<td style="box-sizing: border-box; vertical-align: middle; margin: 0px; padding: 40px;" align="center" valign="middle">
<table style="box-sizing: border-box; border: 1px solid #E0E0E0; border-radius: 3px; margin: 0px; background-color: #ffffff; max-width: 600px !important;" cellspacing="0" cellpadding="0">
<tbody>
<tr style="box-sizing: border-box; margin: 0px;">
<td style="box-sizing: border-box; vertical-align: middle; border-bottom: 1px solid #E0E0E0; margin: 0px; padding: 20px; color: #212121; font-family: Arial; font-size: 20px; line-height: 20px; font-style: normal; font-weight: bold;" valign="middle">${entityType} limit increase request</td>
</tr>
<tr style="box-sizing: border-box; margin: 0px;">
<td style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 16px 24px; color: #212121; font-family: Arial; font-size: 16px; line-height: 24px; font-weight: 400;" valign="top">${userEmail} has reached the maximum number of ${entityType:lowerCase}s allowed and is requesting an increase to the ${entityType:lowerCase} limit.</td>
</tr>
<tr style="box-sizing: border-box; margin: 0px;">
<td style="box-sizing: border-box; vertical-align: top; margin: 0px; padding: 0 24px 16px 24px; color: #212121; font-family: Arial; font-size: 16px; line-height: 24px; font-weight: 400;" valign="top">
<a style="display: inline-block; padding: 10px 16px; border-radius: 4px; background: #106CC8; color: #fff; font-family: Arial; font-size: 14px; line-height: 20px; font-weight: bold; text-decoration: none;" href="${baseUrl}${increaseLimitLink}">${increaseLimitActionLabel}</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>""")
.build())
.build();
public static final DefaultNotification apiFeatureWarningForSysadmin = DefaultNotification.builder()

4
dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java

@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.TenantEntityWithDataDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.EntitiesLimitException;
import org.thingsboard.server.dao.exception.EntitiesLimitExceededException;
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import java.util.HashSet;
@ -124,7 +124,7 @@ public abstract class DataValidator<D extends BaseData<?>> {
EntityType entityType) {
if (!apiLimitService.checkEntitiesLimit(tenantId, entityType)) {
long limit = apiLimitService.getLimit(tenantId, profileConfiguration -> profileConfiguration.getEntitiesLimit(entityType));
throw new EntitiesLimitException(tenantId, entityType, limit);
throw new EntitiesLimitExceededException(tenantId, entityType, limit);
}
}

1
ui-ngx/src/app/shared/models/constants.ts

@ -53,6 +53,7 @@ export const serverErrorCodesTranslations = new Map<number, string>([
[Constants.serverErrorCode.itemNotFound, 'server-error.item-not-found'],
[Constants.serverErrorCode.tooManyRequests, 'server-error.too-many-requests'],
[Constants.serverErrorCode.tooManyUpdates, 'server-error.too-many-updates'],
[Constants.serverErrorCode.entitiesLimitExceeded, 'server-error.entities-limit-exceeded'],
]);
export const MediaBreakpoints = {

3
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -6140,7 +6140,8 @@
"bad-request-params": "Bad request params",
"item-not-found": "Item not found",
"too-many-requests": "Too many requests",
"too-many-updates": "Too many updates"
"too-many-updates": "Too many updates",
"entities-limit-exceeded": "Entities limit exceeded"
},
"tenant": {
"tenant": "Tenant",

Loading…
Cancel
Save