From 620c9e84bb107d94a8cf6895f23234a2735f88b1 Mon Sep 17 00:00:00 2001 From: Alex Morel <amorel@codelutin.com> Date: Tue, 25 Jun 2024 16:45:35 +0200 Subject: [PATCH] Exception Handler - Step 5 : basic Rolback Strategy implementation --- .../libre/scim/core/AbstractScimService.java | 42 +++++++++---------- .../sh/libre/scim/core/GroupScimService.java | 13 +++--- .../java/sh/libre/scim/core/ScimClient.java | 14 +++---- .../sh/libre/scim/core/UserScimService.java | 6 +-- .../ErrorForScimEndpointException.java | 8 ---- .../InconsistentScimDataException.java | 7 ---- .../InconsistentScimMappingException.java | 7 ++++ ...alidResponseFromScimEndpointException.java | 18 ++++++++ ...RollbackOnlyForCriticalErrorsStrategy.java | 32 ++++++++++++++ .../exceptions/RollbackStrategyFactory.java | 4 +- .../core/exceptions/ScimExceptionHandler.java | 2 +- .../META-INF/scim-resource-changelog.xml | 27 +++++++----- 12 files changed, 115 insertions(+), 65 deletions(-) delete mode 100644 src/main/java/sh/libre/scim/core/exceptions/ErrorForScimEndpointException.java delete mode 100644 src/main/java/sh/libre/scim/core/exceptions/InconsistentScimDataException.java create mode 100644 src/main/java/sh/libre/scim/core/exceptions/InconsistentScimMappingException.java create mode 100644 src/main/java/sh/libre/scim/core/exceptions/InvalidResponseFromScimEndpointException.java create mode 100644 src/main/java/sh/libre/scim/core/exceptions/RollbackOnlyForCriticalErrorsStrategy.java diff --git a/src/main/java/sh/libre/scim/core/AbstractScimService.java b/src/main/java/sh/libre/scim/core/AbstractScimService.java index dda7b72..c350702 100644 --- a/src/main/java/sh/libre/scim/core/AbstractScimService.java +++ b/src/main/java/sh/libre/scim/core/AbstractScimService.java @@ -6,8 +6,8 @@ import org.jboss.logging.Logger; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RoleMapperModel; import org.keycloak.storage.user.SynchronizationResult; -import sh.libre.scim.core.exceptions.ErrorForScimEndpointException; -import sh.libre.scim.core.exceptions.InconsistentScimDataException; +import sh.libre.scim.core.exceptions.InconsistentScimMappingException; +import sh.libre.scim.core.exceptions.InvalidResponseFromScimEndpointException; import sh.libre.scim.core.exceptions.SkipOrStopStrategy; import sh.libre.scim.core.exceptions.UnexpectedScimDataException; import sh.libre.scim.jpa.ScimResourceDao; @@ -45,7 +45,7 @@ public abstract class AbstractScimService<K extends RoleMapperModel, S extends R this.skipOrStopStrategy = skipOrStopStrategy; } - public void create(K roleMapperModel) throws InconsistentScimDataException, ErrorForScimEndpointException { + public void create(K roleMapperModel) throws InconsistentScimMappingException, InvalidResponseFromScimEndpointException { if (isMarkedToIgnore(roleMapperModel)) { // Silently return: resource is explicitly marked as to ignore return; @@ -53,14 +53,14 @@ public abstract class AbstractScimService<K extends RoleMapperModel, S extends R // If mapping, then we are trying to recreate a user that was already created by import KeycloakId id = getId(roleMapperModel); if (findMappingById(id).isPresent()) { - throw new InconsistentScimDataException("Trying to create user with id " + id + ": id already exists in Keycloak database"); + throw new InconsistentScimMappingException("Trying to create user with id " + id + ": id already exists in Keycloak database"); } S scimForCreation = scimRequestBodyForCreate(roleMapperModel); EntityOnRemoteScimId externalId = scimClient.create(id, scimForCreation); createMapping(id, externalId); } - public void update(K roleMapperModel) throws InconsistentScimDataException, ErrorForScimEndpointException { + public void update(K roleMapperModel) throws InconsistentScimMappingException, InvalidResponseFromScimEndpointException { if (isMarkedToIgnore(roleMapperModel)) { // Silently return: resource is explicitly marked as to ignore return; @@ -68,22 +68,22 @@ public abstract class AbstractScimService<K extends RoleMapperModel, S extends R KeycloakId keycloakId = getId(roleMapperModel); EntityOnRemoteScimId entityOnRemoteScimId = findMappingById(keycloakId) .map(ScimResourceMapping::getExternalIdAsEntityOnRemoteScimId) - .orElseThrow(() -> new InconsistentScimDataException("Failed to find SCIM mapping for " + keycloakId)); + .orElseThrow(() -> new InconsistentScimMappingException("Failed to find SCIM mapping for " + keycloakId)); S scimForReplace = scimRequestBodyForUpdate(roleMapperModel, entityOnRemoteScimId); scimClient.update(entityOnRemoteScimId, scimForReplace); } - protected abstract S scimRequestBodyForUpdate(K roleMapperModel, EntityOnRemoteScimId externalId) throws InconsistentScimDataException; + protected abstract S scimRequestBodyForUpdate(K roleMapperModel, EntityOnRemoteScimId externalId) throws InconsistentScimMappingException; - public void delete(KeycloakId id) throws InconsistentScimDataException, ErrorForScimEndpointException { + public void delete(KeycloakId id) throws InconsistentScimMappingException, InvalidResponseFromScimEndpointException { ScimResourceMapping resource = findMappingById(id) - .orElseThrow(() -> new InconsistentScimDataException("Failed to delete resource %s, scim mapping not found: ".formatted(id))); + .orElseThrow(() -> new InconsistentScimMappingException("Failed to delete resource %s, scim mapping not found: ".formatted(id))); EntityOnRemoteScimId externalId = resource.getExternalIdAsEntityOnRemoteScimId(); scimClient.delete(externalId); getScimResourceDao().delete(resource); } - public void pushAllResourcesToScim(SynchronizationResult syncRes) throws ErrorForScimEndpointException, InconsistentScimDataException { + public void pushAllResourcesToScim(SynchronizationResult syncRes) throws InvalidResponseFromScimEndpointException, InconsistentScimMappingException { LOGGER.info("[SCIM] Push resources to endpoint " + this.getConfiguration().getEndPoint()); try (Stream<K> resourcesStream = getResourceStream()) { Set<K> resources = resourcesStream.collect(Collectors.toUnmodifiableSet()); @@ -94,14 +94,14 @@ public abstract class AbstractScimService<K extends RoleMapperModel, S extends R } } - public void pullAllResourcesFromScim(SynchronizationResult syncRes) throws UnexpectedScimDataException, InconsistentScimDataException, ErrorForScimEndpointException { + public void pullAllResourcesFromScim(SynchronizationResult syncRes) throws UnexpectedScimDataException, InconsistentScimMappingException, InvalidResponseFromScimEndpointException { LOGGER.info("[SCIM] Pull resources from endpoint " + this.getConfiguration().getEndPoint()); for (S resource : scimClient.listResources()) { pullSingleResourceFromScim(syncRes, resource); } } - private void pushSingleResourceToScim(SynchronizationResult syncRes, K resource, KeycloakId id) throws ErrorForScimEndpointException, InconsistentScimDataException { + private void pushSingleResourceToScim(SynchronizationResult syncRes, K resource, KeycloakId id) throws InvalidResponseFromScimEndpointException, InconsistentScimMappingException { try { LOGGER.infof("[SCIM] Reconciling local resource %s", id); if (shouldIgnoreForScimSynchronization(resource)) { @@ -116,13 +116,13 @@ public abstract class AbstractScimService<K extends RoleMapperModel, S extends R create(resource); } syncRes.increaseUpdated(); - } catch (ErrorForScimEndpointException e) { + } catch (InvalidResponseFromScimEndpointException e) { if (skipOrStopStrategy.allowPartialSynchronizationWhenPushingToScim(this.getConfiguration())) { LOGGER.warn("Error while syncing " + id + " to endpoint " + getConfiguration().getEndPoint()); } else { throw e; } - } catch (InconsistentScimDataException e) { + } catch (InconsistentScimMappingException e) { if (skipOrStopStrategy.allowPartialSynchronizationWhenPushingToScim(this.getConfiguration())) { LOGGER.warn("Inconsistent data for element " + id + " and endpoint " + getConfiguration().getEndPoint()); } else { @@ -132,7 +132,7 @@ public abstract class AbstractScimService<K extends RoleMapperModel, S extends R } - private void pullSingleResourceFromScim(SynchronizationResult syncRes, S resource) throws UnexpectedScimDataException, InconsistentScimDataException, ErrorForScimEndpointException { + private void pullSingleResourceFromScim(SynchronizationResult syncRes, S resource) throws UnexpectedScimDataException, InconsistentScimMappingException, InvalidResponseFromScimEndpointException { try { LOGGER.infof("[SCIM] Reconciling remote resource %s", resource); EntityOnRemoteScimId externalId = resource.getId() @@ -180,13 +180,13 @@ public abstract class AbstractScimService<K extends RoleMapperModel, S extends R } else { throw e; } - } catch (InconsistentScimDataException e) { + } catch (InconsistentScimMappingException e) { if (skipOrStopStrategy.allowPartialSynchronizationWhenPullingFromScim(getConfiguration())) { LOGGER.warn("[SCIM] Skipping element synchronisation because of inconsistent mapping for element " + resource.getId() + " : " + e.getMessage()); } else { throw e; } - } catch (ErrorForScimEndpointException e) { + } catch (InvalidResponseFromScimEndpointException e) { // Can only occur in case of a DELETE_REMOTE conflict action if (skipOrStopStrategy.allowPartialSynchronizationWhenPullingFromScim(getConfiguration())) { LOGGER.warn("[SCIM] Could not delete SCIM resource " + resource.getId() + " during synchronisation"); @@ -198,7 +198,7 @@ public abstract class AbstractScimService<K extends RoleMapperModel, S extends R } - protected abstract S scimRequestBodyForCreate(K roleMapperModel) throws InconsistentScimDataException; + protected abstract S scimRequestBodyForCreate(K roleMapperModel) throws InconsistentScimMappingException; protected abstract KeycloakId getId(K roleMapperModel); @@ -225,13 +225,13 @@ public abstract class AbstractScimService<K extends RoleMapperModel, S extends R protected abstract Stream<K> getResourceStream(); - protected abstract KeycloakId createEntity(S resource) throws UnexpectedScimDataException; + protected abstract KeycloakId createEntity(S resource) throws UnexpectedScimDataException, InconsistentScimMappingException; - protected abstract Optional<KeycloakId> matchKeycloakMappingByScimProperties(S resource) throws InconsistentScimDataException; + protected abstract Optional<KeycloakId> matchKeycloakMappingByScimProperties(S resource) throws InconsistentScimMappingException; protected abstract boolean entityExists(KeycloakId keycloakId); - public void sync(SynchronizationResult syncRes) throws InconsistentScimDataException, ErrorForScimEndpointException, UnexpectedScimDataException { + public void sync(SynchronizationResult syncRes) throws InconsistentScimMappingException, InvalidResponseFromScimEndpointException, UnexpectedScimDataException { if (this.scimProviderConfiguration.isPullFromScimSynchronisationActivated()) { this.pullAllResourcesFromScim(syncRes); } diff --git a/src/main/java/sh/libre/scim/core/GroupScimService.java b/src/main/java/sh/libre/scim/core/GroupScimService.java index 92857ec..35daece 100644 --- a/src/main/java/sh/libre/scim/core/GroupScimService.java +++ b/src/main/java/sh/libre/scim/core/GroupScimService.java @@ -10,7 +10,7 @@ import org.jboss.logging.Logger; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.UserModel; -import sh.libre.scim.core.exceptions.InconsistentScimDataException; +import sh.libre.scim.core.exceptions.InconsistentScimMappingException; import sh.libre.scim.core.exceptions.SkipOrStopStrategy; import sh.libre.scim.core.exceptions.UnexpectedScimDataException; import sh.libre.scim.jpa.ScimResourceMapping; @@ -55,7 +55,7 @@ public class GroupScimService extends AbstractScimService<GroupModel, Group> { } @Override - protected KeycloakId createEntity(Group resource) throws UnexpectedScimDataException { + protected KeycloakId createEntity(Group resource) throws UnexpectedScimDataException, InconsistentScimMappingException { String displayName = resource.getDisplayName() .filter(StringUtils::isNotBlank) .orElseThrow(() -> new UnexpectedScimDataException("Remote Scim group has empty name, can't create. Resource id = %s".formatted(resource.getId()))); @@ -68,8 +68,7 @@ public class GroupScimService extends AbstractScimService<GroupModel, Group> { .orElseThrow(() -> new UnexpectedScimDataException("can't create group member for group '%s' without id: ".formatted(displayName) + resource)); KeycloakId userId = getScimResourceDao().findUserByExternalId(externalId) .map(ScimResourceMapping::getIdAsKeycloakId) - // TODO Exception handling : here if think this is a InconsistentScimData : Scim member is valid, but not mapped yet in our keycloak - .orElseThrow(() -> new UnexpectedScimDataException("can't find mapping for group member %s".formatted(externalId))); + .orElseThrow(() -> new InconsistentScimMappingException("can't find mapping for group member %s".formatted(externalId))); UserModel userModel = getKeycloakDao().getUserById(userId); userModel.joinGroup(group); } @@ -88,7 +87,7 @@ public class GroupScimService extends AbstractScimService<GroupModel, Group> { } @Override - protected Group scimRequestBodyForCreate(GroupModel groupModel) throws InconsistentScimDataException { + protected Group scimRequestBodyForCreate(GroupModel groupModel) throws InconsistentScimMappingException { Set<KeycloakId> members = getKeycloakDao().getGroupMembers(groupModel); Group group = new Group(); group.setExternalId(groupModel.getId()); @@ -108,7 +107,7 @@ public class GroupScimService extends AbstractScimService<GroupModel, Group> { if (skipOrStopStrategy.allowMissingMembersWhenPushingGroupToScim(this.getConfiguration())) { LOGGER.warn(message); } else { - throw new InconsistentScimDataException(message); + throw new InconsistentScimMappingException(message); } } } @@ -116,7 +115,7 @@ public class GroupScimService extends AbstractScimService<GroupModel, Group> { } @Override - protected Group scimRequestBodyForUpdate(GroupModel groupModel, EntityOnRemoteScimId externalId) throws InconsistentScimDataException { + protected Group scimRequestBodyForUpdate(GroupModel groupModel, EntityOnRemoteScimId externalId) throws InconsistentScimMappingException { Group group = scimRequestBodyForCreate(groupModel); group.setId(externalId.asString()); Meta meta = newMetaLocation(externalId); diff --git a/src/main/java/sh/libre/scim/core/ScimClient.java b/src/main/java/sh/libre/scim/core/ScimClient.java index 9e0d136..7649ccb 100644 --- a/src/main/java/sh/libre/scim/core/ScimClient.java +++ b/src/main/java/sh/libre/scim/core/ScimClient.java @@ -12,7 +12,7 @@ import io.github.resilience4j.retry.RetryConfig; import io.github.resilience4j.retry.RetryRegistry; import jakarta.ws.rs.ProcessingException; import org.jboss.logging.Logger; -import sh.libre.scim.core.exceptions.ErrorForScimEndpointException; +import sh.libre.scim.core.exceptions.InvalidResponseFromScimEndpointException; import java.util.Collections; import java.util.HashMap; @@ -64,7 +64,7 @@ public class ScimClient<S extends ResourceNode> implements AutoCloseable { return new ScimClient(scimRequestBuilder, scimResourceType); } - public EntityOnRemoteScimId create(KeycloakId id, S scimForCreation) throws ErrorForScimEndpointException { + public EntityOnRemoteScimId create(KeycloakId id, S scimForCreation) throws InvalidResponseFromScimEndpointException { Optional<String> scimForCreationId = scimForCreation.getId(); if (scimForCreationId.isPresent()) { throw new IllegalArgumentException( @@ -82,12 +82,12 @@ public class ScimClient<S extends ResourceNode> implements AutoCloseable { S resource = response.getResource(); return resource.getId() .map(EntityOnRemoteScimId::new) - .orElseThrow(() -> new ErrorForScimEndpointException("Created SCIM resource does not have id")); + .orElseThrow(() -> new InvalidResponseFromScimEndpointException(response, "Created SCIM resource does not have id")); } - private void checkResponseIsSuccess(ServerResponse<S> response) throws ErrorForScimEndpointException { + private void checkResponseIsSuccess(ServerResponse<S> response) throws InvalidResponseFromScimEndpointException { if (!response.isSuccess()) { - throw new ErrorForScimEndpointException("Server answered with status " + response.getResponseBody() + ": " + response.getResponseBody()); + throw new InvalidResponseFromScimEndpointException(response, "Server answered with status " + response.getResponseBody() + ": " + response.getResponseBody()); } } @@ -99,7 +99,7 @@ public class ScimClient<S extends ResourceNode> implements AutoCloseable { return scimResourceType.getResourceClass(); } - public void update(EntityOnRemoteScimId externalId, S scimForReplace) throws ErrorForScimEndpointException { + public void update(EntityOnRemoteScimId externalId, S scimForReplace) throws InvalidResponseFromScimEndpointException { Retry retry = retryRegistry.retry("replace-%s".formatted(externalId.asString())); // TODO Exception handling : check that all exceptions are wrapped in server response ServerResponse<S> response = retry.executeSupplier(() -> scimRequestBuilder @@ -110,7 +110,7 @@ public class ScimClient<S extends ResourceNode> implements AutoCloseable { checkResponseIsSuccess(response); } - public void delete(EntityOnRemoteScimId externalId) throws ErrorForScimEndpointException { + public void delete(EntityOnRemoteScimId externalId) throws InvalidResponseFromScimEndpointException { Retry retry = retryRegistry.retry("delete-%s".formatted(externalId.asString())); // TODO Exception handling : check that all exceptions are wrapped in server response ServerResponse<S> response = retry.executeSupplier(() -> scimRequestBuilder diff --git a/src/main/java/sh/libre/scim/core/UserScimService.java b/src/main/java/sh/libre/scim/core/UserScimService.java index b74e0b8..54c8345 100644 --- a/src/main/java/sh/libre/scim/core/UserScimService.java +++ b/src/main/java/sh/libre/scim/core/UserScimService.java @@ -13,7 +13,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.RoleMapperModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; -import sh.libre.scim.core.exceptions.InconsistentScimDataException; +import sh.libre.scim.core.exceptions.InconsistentScimMappingException; import sh.libre.scim.core.exceptions.SkipOrStopStrategy; import sh.libre.scim.core.exceptions.UnexpectedScimDataException; @@ -43,7 +43,7 @@ public class UserScimService extends AbstractScimService<UserModel, User> { } @Override - protected Optional<KeycloakId> matchKeycloakMappingByScimProperties(User resource) throws InconsistentScimDataException { + protected Optional<KeycloakId> matchKeycloakMappingByScimProperties(User resource) throws InconsistentScimMappingException { Optional<KeycloakId> matchedByUsername = resource.getUserName() .map(getKeycloakDao()::getUserByUsername) .map(this::getId); @@ -55,7 +55,7 @@ public class UserScimService extends AbstractScimService<UserModel, User> { if (matchedByUsername.isPresent() && matchedByEmail.isPresent() && !matchedByUsername.equals(matchedByEmail)) { - throw new InconsistentScimDataException("Found 2 possible users for remote user " + matchedByUsername.get() + " - " + matchedByEmail.get()); + throw new InconsistentScimMappingException("Found 2 possible users for remote user " + matchedByUsername.get() + " - " + matchedByEmail.get()); } if (matchedByUsername.isPresent()) { return matchedByUsername; diff --git a/src/main/java/sh/libre/scim/core/exceptions/ErrorForScimEndpointException.java b/src/main/java/sh/libre/scim/core/exceptions/ErrorForScimEndpointException.java deleted file mode 100644 index 1705e99..0000000 --- a/src/main/java/sh/libre/scim/core/exceptions/ErrorForScimEndpointException.java +++ /dev/null @@ -1,8 +0,0 @@ -package sh.libre.scim.core.exceptions; - -public class ErrorForScimEndpointException extends ScimPropagationException { - - public ErrorForScimEndpointException(String message) { - super(message); - } -} diff --git a/src/main/java/sh/libre/scim/core/exceptions/InconsistentScimDataException.java b/src/main/java/sh/libre/scim/core/exceptions/InconsistentScimDataException.java deleted file mode 100644 index 947a9f1..0000000 --- a/src/main/java/sh/libre/scim/core/exceptions/InconsistentScimDataException.java +++ /dev/null @@ -1,7 +0,0 @@ -package sh.libre.scim.core.exceptions; - -public class InconsistentScimDataException extends ScimPropagationException { - public InconsistentScimDataException(String message) { - super(message); - } -} diff --git a/src/main/java/sh/libre/scim/core/exceptions/InconsistentScimMappingException.java b/src/main/java/sh/libre/scim/core/exceptions/InconsistentScimMappingException.java new file mode 100644 index 0000000..44f7eb4 --- /dev/null +++ b/src/main/java/sh/libre/scim/core/exceptions/InconsistentScimMappingException.java @@ -0,0 +1,7 @@ +package sh.libre.scim.core.exceptions; + +public class InconsistentScimMappingException extends ScimPropagationException { + public InconsistentScimMappingException(String message) { + super(message); + } +} diff --git a/src/main/java/sh/libre/scim/core/exceptions/InvalidResponseFromScimEndpointException.java b/src/main/java/sh/libre/scim/core/exceptions/InvalidResponseFromScimEndpointException.java new file mode 100644 index 0000000..27c5c97 --- /dev/null +++ b/src/main/java/sh/libre/scim/core/exceptions/InvalidResponseFromScimEndpointException.java @@ -0,0 +1,18 @@ +package sh.libre.scim.core.exceptions; + +import de.captaingoldfish.scim.sdk.client.response.ServerResponse; + +public class InvalidResponseFromScimEndpointException extends ScimPropagationException { + + private final ServerResponse response; + + public InvalidResponseFromScimEndpointException(ServerResponse response, String message) { + super(message); + this.response = response; + } + + public ServerResponse getResponse() { + return response; + } + +} diff --git a/src/main/java/sh/libre/scim/core/exceptions/RollbackOnlyForCriticalErrorsStrategy.java b/src/main/java/sh/libre/scim/core/exceptions/RollbackOnlyForCriticalErrorsStrategy.java new file mode 100644 index 0000000..fae0926 --- /dev/null +++ b/src/main/java/sh/libre/scim/core/exceptions/RollbackOnlyForCriticalErrorsStrategy.java @@ -0,0 +1,32 @@ +package sh.libre.scim.core.exceptions; + +import sh.libre.scim.core.ScrimEndPointConfiguration; + +public class RollbackOnlyForCriticalErrorsStrategy implements RollbackStrategy { + + private boolean shouldRollback(InvalidResponseFromScimEndpointException e) { + int httpStatus = e.getResponse().getHttpStatus(); + return httpStatus == 500; + } + + @Override + public boolean shouldRollback(ScrimEndPointConfiguration configuration, ScimPropagationException e) { + if (e instanceof InvalidResponseFromScimEndpointException invalidResponseFromScimEndpointException) { + return shouldRollback(invalidResponseFromScimEndpointException); + } + if (e instanceof InconsistentScimMappingException) { + // Occurs when mapping between a SCIM resource and a keycloak user failed (missing, ambiguous..) + // Log can be sufficient here, no rollback required + return false; + } + if (e instanceof UnexpectedScimDataException) { + // Occurs when a SCIM endpoint sends invalid date (e.g. group with empty name, user without ids...) + // No rollback required : we cannot recover. This needs to be fixed in the SCIM endpoint data + return false; + } + // Should not occur + throw new IllegalStateException("Unkown ScimPropagationException", e); + } + + +} diff --git a/src/main/java/sh/libre/scim/core/exceptions/RollbackStrategyFactory.java b/src/main/java/sh/libre/scim/core/exceptions/RollbackStrategyFactory.java index b2df155..23651a7 100644 --- a/src/main/java/sh/libre/scim/core/exceptions/RollbackStrategyFactory.java +++ b/src/main/java/sh/libre/scim/core/exceptions/RollbackStrategyFactory.java @@ -9,13 +9,15 @@ public class RollbackStrategyFactory { return switch (approach) { case ALWAYS_ROLLBACK -> new AlwaysRollbackStrategy(); case NEVER_ROLLBACK -> new NeverRollbackStrategy(); + case CRITICAL_ONLY_ROLLBACK -> new RollbackOnlyForCriticalErrorsStrategy(); }; } public enum RollbackApproach { - ALWAYS_ROLLBACK, NEVER_ROLLBACK + ALWAYS_ROLLBACK, NEVER_ROLLBACK, CRITICAL_ONLY_ROLLBACK } + private static final class AlwaysRollbackStrategy implements RollbackStrategy { @Override diff --git a/src/main/java/sh/libre/scim/core/exceptions/ScimExceptionHandler.java b/src/main/java/sh/libre/scim/core/exceptions/ScimExceptionHandler.java index 626c04e..7a6d6bf 100644 --- a/src/main/java/sh/libre/scim/core/exceptions/ScimExceptionHandler.java +++ b/src/main/java/sh/libre/scim/core/exceptions/ScimExceptionHandler.java @@ -17,7 +17,7 @@ public class ScimExceptionHandler { private final RollbackStrategy rollbackStrategy; public ScimExceptionHandler(KeycloakSession session) { - this(session, RollbackStrategyFactory.create(RollbackStrategyFactory.RollbackApproach.NEVER_ROLLBACK)); + this(session, RollbackStrategyFactory.create(RollbackStrategyFactory.RollbackApproach.CRITICAL_ONLY_ROLLBACK)); } public ScimExceptionHandler(KeycloakSession session, RollbackStrategy rollbackStrategy) { diff --git a/src/main/resources/META-INF/scim-resource-changelog.xml b/src/main/resources/META-INF/scim-resource-changelog.xml index 45be732..cf300fa 100644 --- a/src/main/resources/META-INF/scim-resource-changelog.xml +++ b/src/main/resources/META-INF/scim-resource-changelog.xml @@ -1,28 +1,35 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"> +<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"> <changeSet author="contact@indiehosters.net" id="scim-resource-1.0"> - <createTable tableName="SCIM_RESOURCE"> + <createTable tableName="SCIM_RESOURCE_MAPPING"> <column name="ID" type="VARCHAR(36)"> - <constraints nullable="false" /> + <constraints nullable="false"/> </column> <column name="REALM_ID" type="VARCHAR(36)"> - <constraints nullable="false" /> + <constraints nullable="false"/> </column> <column name="TYPE" type="VARCHAR(36)"> - <constraints nullable="false" /> + <constraints nullable="false"/> </column> <column name="COMPONENT_ID" type="VARCHAR(36)"> - <constraints nullable="false" /> + <constraints nullable="false"/> </column> <column name="EXTERNAL_ID" type="VARCHAR(36)"> - <constraints nullable="false" /> + <constraints nullable="false"/> </column> </createTable> - <addPrimaryKey constraintName="PK_SCIM_RESOURCE" tableName="SCIM_RESOURCE" columnNames="ID,REALM_ID,TYPE,COMPONENT_ID,EXTERNAL_ID" /> - <addForeignKeyConstraint baseTableName="SCIM_RESOURCE" baseColumnNames="REALM_ID" constraintName="FK_SCIM_RESOURCE_REALM" referencedTableName="REALM" referencedColumnNames="ID" onDelete="CASCADE" onUpdate="CASCADE" /> - <addForeignKeyConstraint baseTableName="SCIM_RESOURCE" baseColumnNames="COMPONENT_ID" constraintName="FK_SCIM_RESOURCE_COMPONENT" referencedTableName="COMPONENT" referencedColumnNames="ID" onDelete="CASCADE" onUpdate="CASCADE" /> + <addPrimaryKey constraintName="PK_SCIM_RESOURCE" tableName="SCIM_RESOURCE" + columnNames="ID,REALM_ID,TYPE,COMPONENT_ID,EXTERNAL_ID"/> + <addForeignKeyConstraint baseTableName="SCIM_RESOURCE" baseColumnNames="REALM_ID" + constraintName="FK_SCIM_RESOURCE_REALM" referencedTableName="REALM" + referencedColumnNames="ID" onDelete="CASCADE" onUpdate="CASCADE"/> + <addForeignKeyConstraint baseTableName="SCIM_RESOURCE" baseColumnNames="COMPONENT_ID" + constraintName="FK_SCIM_RESOURCE_COMPONENT" referencedTableName="COMPONENT" + referencedColumnNames="ID" onDelete="CASCADE" onUpdate="CASCADE"/> </changeSet> </databaseChangeLog> \ No newline at end of file -- GitLab