diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java
index ce7ea1efdd..58b1e8d422 100644
--- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java
+++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java
@@ -15,6 +15,7 @@
*/
package org.thingsboard.server.exception;
+import jakarta.persistence.PersistenceException;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
@@ -52,6 +53,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.DaoUtil;
import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException;
import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
import org.thingsboard.server.service.security.exception.UserPasswordExpiredException;
@@ -162,8 +164,8 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand
handleAuthenticationException(authenticationException, response);
} else if (exception instanceof MaxPayloadSizeExceededException maxPayloadSizeExceededException) {
handleMaxPayloadSizeExceededException(response, maxPayloadSizeExceededException);
- } else if (exception instanceof DataAccessException e) {
- handleDatabaseException(e, response);
+ } else if (exception instanceof DataAccessException || exception instanceof PersistenceException) {
+ handleDatabaseException(exception, response);
} else {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
JacksonUtil.writeValue(response.getWriter(), ThingsboardErrorResponse.of(exception.getMessage(),
@@ -217,8 +219,17 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand
private void handleDatabaseException(Throwable databaseException, HttpServletResponse response) throws IOException {
ThingsboardErrorResponse errorResponse;
- if (databaseException instanceof ConstraintViolationException) {
- errorResponse = ThingsboardErrorResponse.of(ExceptionUtils.getRootCause(databaseException).getMessage(), ThingsboardErrorCode.BAD_REQUEST_PARAMS, HttpStatus.BAD_REQUEST);
+ ConstraintViolationException constraintViolationException = DaoUtil.extractConstraintViolation(databaseException);
+ if (constraintViolationException != null) {
+ log.debug("Constraint violation: {}", ExceptionUtils.getRootCauseMessage(databaseException));
+ String constraintName = constraintViolationException.getConstraintName();
+ String userMessage;
+ if (constraintName != null && !constraintName.isEmpty()) {
+ userMessage = "Constraint violation: " + constraintName;
+ } else {
+ userMessage = "Constraint violation";
+ }
+ errorResponse = ThingsboardErrorResponse.of(userMessage, ThingsboardErrorCode.BAD_REQUEST_PARAMS, HttpStatus.BAD_REQUEST);
} else {
log.warn("Database error: {} - {}", databaseException.getClass().getSimpleName(), ExceptionUtils.getRootCauseMessage(databaseException));
errorResponse = ThingsboardErrorResponse.of("Database error", ThingsboardErrorCode.DATABASE, HttpStatus.INTERNAL_SERVER_ERROR);
diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
index 0ff227384e..6a658ffd66 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
@@ -1162,7 +1162,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
assertThat(findRelationsByTo(entityTo)).hasSize(1);
doDelete(urlDelete)
- .andExpect(status().isInternalServerError());
+ .andExpect(status().isBadRequest());
assertThat(findRelationsByTo(entityTo)).hasSize(1);
} finally {
diff --git a/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
index fb86ea7b43..aeca72c739 100644
--- a/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
@@ -43,6 +43,7 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.exception.TenantNotFoundException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@@ -936,6 +937,26 @@ public class TenantControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService);
}
+ @Test
+ public void testSaveTenantWithNonExistentTenantProfileId() throws Exception {
+ loginSysAdmin();
+ Tenant tenant = new Tenant();
+ tenant.setTitle("My tenant");
+ tenant.setTenantProfileId(new TenantProfileId(UUID.randomUUID()));
+
+ String responseBody = doPost("/api/tenant", tenant)
+ .andExpect(status().isBadRequest())
+ .andReturn().getResponse().getContentAsString();
+
+ // Verify sanitized message format
+ assertThat(responseBody).contains("Constraint violation: fk_tenant_profile");
+ // Verify raw SQL details are not returned
+ assertThat(responseBody).doesNotContain("could not execute statement");
+ assertThat(responseBody).doesNotContain("insert or update on table");
+ assertThat(responseBody).doesNotContain("tenant_profile_id");
+ assertThat(responseBody).doesNotContain("is not present in table");
+ }
+
private void testBroadcastEntityStateChangeEventNeverTenant() {
Mockito.verify(tbClusterService, never()).onTenantChange(Mockito.any(Tenant.class),
Mockito.isNull());
diff --git a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
index a19ba6c0e7..4b469851cd 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
@@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao;
+import org.hibernate.exception.ConstraintViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
@@ -202,4 +203,13 @@ public final class DaoUtil {
.collect(Collectors.toList());
}
+ public static ConstraintViolationException extractConstraintViolation(Throwable t) {
+ if (t instanceof ConstraintViolationException cve) {
+ return cve;
+ } else if (t != null && t.getCause() instanceof ConstraintViolationException cve) {
+ return cve;
+ }
+ return null;
+ }
+
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html
index 4f95435f35..083178ddb3 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html
@@ -45,8 +45,7 @@
{{ 'widgets.maps.overlays.markers' | translate }}
{{ 'widgets.maps.overlays.polygons' | translate }}
{{ 'widgets.maps.overlays.circles' | translate }}
- //todo translation
- Polylines
+ {{ 'widgets.maps.overlays.polylines' | translate }}