diff --git a/commons-rest/errors/src/main/java/org/eclipse/kapua/commons/rest/errors/KapuaIntegrityConstraintViolationExceptionMapper.java b/commons-rest/errors/src/main/java/org/eclipse/kapua/commons/rest/errors/KapuaIntegrityConstraintViolationExceptionMapper.java new file mode 100644 index 00000000000..493d2d6fe93 --- /dev/null +++ b/commons-rest/errors/src/main/java/org/eclipse/kapua/commons/rest/errors/KapuaIntegrityConstraintViolationExceptionMapper.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2018, 2022 Eurotech and/or its affiliates and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Eurotech - initial API and implementation + *******************************************************************************/ +package org.eclipse.kapua.commons.rest.errors; + +import org.eclipse.kapua.kapuaIntegrityConstraintViolationException; +import org.eclipse.kapua.commons.rest.model.errors.ExceptionInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +@Provider +public class KapuaIntegrityConstraintViolationExceptionMapper implements ExceptionMapper { + private static final Logger LOG = LoggerFactory.getLogger(kapuaIntegrityConstraintViolationException.class); + + private static final Status STATUS = Status.CONFLICT; + @Inject + public ExceptionConfigurationProvider exceptionConfigurationProvider; + + @Override + public Response toResponse(kapuaIntegrityConstraintViolationException kapuaIntegrityConstraintViolationException) { + final boolean showStackTrace = exceptionConfigurationProvider.showStackTrace(); + LOG.error(kapuaIntegrityConstraintViolationException.getMessage(), kapuaIntegrityConstraintViolationException); + return Response + .status(STATUS) + .entity(new ExceptionInfo(STATUS.getStatusCode(), kapuaIntegrityConstraintViolationException, showStackTrace)) + .build(); + } + +} diff --git a/commons/src/main/java/org/eclipse/kapua/commons/util/KapuaExceptionUtils.java b/commons/src/main/java/org/eclipse/kapua/commons/util/KapuaExceptionUtils.java index b7c59516cfd..8eb4cbbff4c 100644 --- a/commons/src/main/java/org/eclipse/kapua/commons/util/KapuaExceptionUtils.java +++ b/commons/src/main/java/org/eclipse/kapua/commons/util/KapuaExceptionUtils.java @@ -16,11 +16,13 @@ import org.eclipse.kapua.KapuaException; import org.eclipse.kapua.KapuaIllegalNullArgumentException; import org.eclipse.kapua.KapuaOptimisticLockingException; +import org.eclipse.kapua.kapuaIntegrityConstraintViolationException; import org.eclipse.persistence.exceptions.DatabaseException; import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; import javax.persistence.RollbackException; +import java.sql.SQLIntegrityConstraintViolationException; /** * Exception utilities @@ -99,6 +101,12 @@ public static KapuaException convertPersistenceException(Exception he) { } break; + default: { + if (cve.getInternalException() instanceof SQLIntegrityConstraintViolationException) { + String message = cve.getMessage().contains("FOREIGN KEY") ? "This entity relates to other entities and cannot be deleted." : ""; + ee = new kapuaIntegrityConstraintViolationException(message); + } + } } } } diff --git a/commons/src/test/java/org/eclipse/kapua/commons/util/KapuaExceptionUtilsTest.java b/commons/src/test/java/org/eclipse/kapua/commons/util/KapuaExceptionUtilsTest.java index 6c0a0aa882c..2ec2ec5c3bf 100644 --- a/commons/src/test/java/org/eclipse/kapua/commons/util/KapuaExceptionUtilsTest.java +++ b/commons/src/test/java/org/eclipse/kapua/commons/util/KapuaExceptionUtilsTest.java @@ -13,6 +13,7 @@ package org.eclipse.kapua.commons.util; import java.lang.reflect.Constructor; +import java.sql.SQLIntegrityConstraintViolationException; import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; @@ -22,6 +23,7 @@ import org.eclipse.kapua.KapuaException; import org.eclipse.kapua.KapuaIllegalNullArgumentException; import org.eclipse.kapua.KapuaOptimisticLockingException; +import org.eclipse.kapua.kapuaIntegrityConstraintViolationException; import org.eclipse.kapua.qa.markers.junit.JUnitTests; import org.eclipse.persistence.exceptions.DatabaseException; @@ -111,9 +113,21 @@ public void convertPersistenceDatabaseExceptionTest() { Mockito.when(mockedDatabaseException.getInternalException()).thenReturn(mockedDatabaseException); Assert.assertEquals("ComparisonFailure not expected for: " + exception,kapuaException.toString(), KapuaExceptionUtils.convertPersistenceException(exception).toString()); - Mockito.verify(mockedDatabaseException, Mockito.times(12)).getInternalException(); - Mockito.verify(mockedDatabaseException, Mockito.times(12)).getMessage(); - Mockito.verify(mockedDatabaseException, Mockito.times(13)).getErrorCode(); + //SQL foreign key constraint violation + SQLIntegrityConstraintViolationException mockedDatabaseException2 = Mockito.mock(SQLIntegrityConstraintViolationException.class); + Mockito.when(mockedDatabaseException.getInternalException()).thenReturn(mockedDatabaseException2); + Mockito.when(mockedDatabaseException.getMessage()).thenReturn("FOREIGN KEY"); + kapuaIntegrityConstraintViolationException ke = new kapuaIntegrityConstraintViolationException("This entity relates to other entities and cannot be deleted."); + Assert.assertEquals("ComparisonFailure not expected for: " + exception,ke.toString(), KapuaExceptionUtils.convertPersistenceException(exception).toString()); + + //generic SQL constraint violation + Mockito.when(mockedDatabaseException.getMessage()).thenReturn("another message different from for3ign key but always SQL integrity constraint violation stuff"); + kapuaIntegrityConstraintViolationException ke2 = new kapuaIntegrityConstraintViolationException(""); + Assert.assertEquals("ComparisonFailure not expected for: " + exception,ke2.toString(), KapuaExceptionUtils.convertPersistenceException(exception).toString()); + + Mockito.verify(mockedDatabaseException, Mockito.times(15)).getInternalException(); + Mockito.verify(mockedDatabaseException, Mockito.times(14)).getMessage(); + Mockito.verify(mockedDatabaseException, Mockito.times(15)).getErrorCode(); } @Test diff --git a/qa/integration/src/test/resources/features/authorization/DomainService.feature b/qa/integration/src/test/resources/features/authorization/DomainService.feature index ae10890b4bd..0d603bb206d 100644 --- a/qa/integration/src/test/resources/features/authorization/DomainService.feature +++ b/qa/integration/src/test/resources/features/authorization/DomainService.feature @@ -70,7 +70,7 @@ Feature: Domain Service tests | test_name_1 | read,write | Then A domain was created And The domain matches the creator - Given I expect the exception "KapuaException" with the text "Error during Persistence Operation" + Given I expect the exception "kapuaIntegrityConstraintViolationException" with the text "Entity constraint violation error." When I create the domain | name | actions | | test_name_1 | read,write | diff --git a/qa/integration/src/test/resources/features/connection/UserCouplingI9n.feature b/qa/integration/src/test/resources/features/connection/UserCouplingI9n.feature index 24d464404a3..9b78f43b7fd 100644 --- a/qa/integration/src/test/resources/features/connection/UserCouplingI9n.feature +++ b/qa/integration/src/test/resources/features/connection/UserCouplingI9n.feature @@ -448,7 +448,7 @@ Feature: User Coupling And I set the reserved user for the connection from device "device-1" in account "test-acc-1" to "test-user-1" Then I set the user coupling mode for the connection from device "device-2" in account "test-acc-1" to "STRICT" # Try to set a duplicate reserved user - Given I expect the exception "KapuaException" with the text "Error during Persistence Operation" + Given I expect the exception "kapuaIntegrityConstraintViolationException" with the text "Entity constraint violation error." When I set the reserved user for the connection from device "device-2" in account "test-acc-1" to "test-user-1" Then An exception was thrown # Reserved users must be unique! @@ -873,7 +873,7 @@ Feature: User Coupling And I set the reserved user for the connection from device "device-1" in account "test-acc-1" to "test-user-1" Then I set the user coupling mode for the connection from device "device-2" in account "test-acc-1" to "STRICT" # Try to set a duplicate reserved user - Given I expect the exception "KapuaException" with the text "Error during Persistence Operation" + Given I expect the exception "kapuaIntegrityConstraintViolationException" with the text "Entity constraint violation error." When I set the reserved user for the connection from device "device-2" in account "test-acc-1" to "test-user-1" Then An exception was thrown # Reserved users must be unique! diff --git a/service/api/src/main/java/org/eclipse/kapua/KapuaErrorCodes.java b/service/api/src/main/java/org/eclipse/kapua/KapuaErrorCodes.java index 75a07354d13..6b92a09b55f 100644 --- a/service/api/src/main/java/org/eclipse/kapua/KapuaErrorCodes.java +++ b/service/api/src/main/java/org/eclipse/kapua/KapuaErrorCodes.java @@ -139,5 +139,13 @@ public enum KapuaErrorCodes implements KapuaErrorCode { * Some parsing failed for some reason * @since 2.0.0 */ - PARSING_ERROR + PARSING_ERROR, + + /** + * Sql integrity has been violated for some reason + * @since 2.0.0 + */ + DATASTORE_INTEGRITY_VIOLATION + + } diff --git a/service/api/src/main/java/org/eclipse/kapua/kapuaIntegrityConstraintViolationException.java b/service/api/src/main/java/org/eclipse/kapua/kapuaIntegrityConstraintViolationException.java new file mode 100644 index 00000000000..64ea723c0f5 --- /dev/null +++ b/service/api/src/main/java/org/eclipse/kapua/kapuaIntegrityConstraintViolationException.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2016, 2022 Eurotech and/or its affiliates and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Eurotech - initial API and implementation + *******************************************************************************/ +package org.eclipse.kapua; + +/** + * kapuaIntegrityConstraintViolationException is thrown when the integrity constraints of the underlying datastore have been violated + * + * @since 2.0.0 + */ +public class kapuaIntegrityConstraintViolationException extends KapuaException { + + /** + * Constructor. + * + * @since 2.0.0 + */ + public kapuaIntegrityConstraintViolationException() { + super(KapuaErrorCodes.DATASTORE_INTEGRITY_VIOLATION); + } + + /** + * Constructor. + * + * @since 2.0.0 + */ + public kapuaIntegrityConstraintViolationException(String detailedMessage) { + super(KapuaErrorCodes.DATASTORE_INTEGRITY_VIOLATION, detailedMessage); + } +} diff --git a/service/api/src/main/resources/kapua-service-error-messages.properties b/service/api/src/main/resources/kapua-service-error-messages.properties index 1fcf0fcb8ea..2ebbb483f36 100644 --- a/service/api/src/main/resources/kapua-service-error-messages.properties +++ b/service/api/src/main/resources/kapua-service-error-messages.properties @@ -39,4 +39,5 @@ UNAUTHENTICATED=No authenticated Subject found in context. # Deprecated codes USER_ALREADY_RESERVED_BY_ANOTHER_CONNECTION=This user is already reserved for another connection. Please select different user for this connection. DEVICE_NOT_FOUND=The selected devices were not found. Please refresh device list. -PARSING_ERROR=Error while parsing: {0} \ No newline at end of file +PARSING_ERROR=Error while parsing: {0} +DATASTORE_INTEGRITY_VIOLATION=Entity constraint violation error. {0} \ No newline at end of file