You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by bd...@apache.org on 2022/09/07 23:15:48 UTC

[directory-scimple] 01/01: DRY out BaseResourceTypeResourceImpl

This is an automated email from the ASF dual-hosted git repository.

bdemers pushed a commit to branch dry-base-resource
in repository https://gitbox.apache.org/repos/asf/directory-scimple.git

commit 4e39a057be1daacedce98ab350dd02fc8e225965
Author: Brian Demers <bd...@apache.org>
AuthorDate: Wed Sep 7 19:15:41 2022 -0400

    DRY out BaseResourceTypeResourceImpl
    
    (and SelfResourceImpl)
    
    Adds new exception mappers
---
 .../server/exception/GenericExceptionMapper.java   |  44 ++
 .../ResourceExceptionMapper.java}                  |  30 +-
 .../ScimExceptionMapper.java}                      |  14 +-
 .../scim/server/exception/ScimServerException.java |  61 --
 .../UnsupportedOperationExceptionMapper.java       |  44 ++
 .../WebApplicationExceptionMapper.java             |   2 +-
 .../server/rest/BaseResourceTypeResourceImpl.java  | 711 ++++++---------------
 .../scim/server/rest/ScimResourceHelper.java       |   8 +-
 .../scim/server/rest/SelfResourceImpl.java         |  60 +-
 .../rest/BaseResourceTypeResourceImplTest.java     | 123 +---
 .../scim/server/rest/SelfResourceImplTest.java     |  15 +-
 .../scim/protocol/BaseResourceTypeResource.java    |  15 +-
 .../directory/scim/protocol/SelfResource.java      |  12 +-
 .../scim/protocol/exception/ScimException.java     |  10 +
 .../attribute/AttributeReferenceListWrapper.java   |   8 +
 15 files changed, 405 insertions(+), 752 deletions(-)

diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/exception/GenericExceptionMapper.java b/scim-server/src/main/java/org/apache/directory/scim/server/exception/GenericExceptionMapper.java
new file mode 100644
index 00000000..ef0c24f9
--- /dev/null
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/exception/GenericExceptionMapper.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.directory.scim.server.exception;
+
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+import org.apache.directory.scim.protocol.Constants;
+import org.apache.directory.scim.protocol.data.ErrorResponse;
+
+@Provider
+@Produces({Constants.SCIM_CONTENT_TYPE, MediaType.APPLICATION_JSON})
+public class GenericExceptionMapper implements ExceptionMapper<Throwable> {
+
+  @Override
+  public Response toResponse(Throwable throwable) {
+    ErrorResponse em = new ErrorResponse(Response.Status.INTERNAL_SERVER_ERROR, throwable.getMessage());
+
+    Response response = em.toResponse();
+    response.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE, Constants.SCIM_CONTENT_TYPE);
+
+    return response;
+  }
+}
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/rest/WebApplicationExceptionMapper.java b/scim-server/src/main/java/org/apache/directory/scim/server/exception/ResourceExceptionMapper.java
similarity index 59%
copy from scim-server/src/main/java/org/apache/directory/scim/server/rest/WebApplicationExceptionMapper.java
copy to scim-server/src/main/java/org/apache/directory/scim/server/exception/ResourceExceptionMapper.java
index cd96d801..cc49b4b7 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/rest/WebApplicationExceptionMapper.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/exception/ResourceExceptionMapper.java
@@ -17,29 +17,43 @@
 * under the License.
 */
 
-package org.apache.directory.scim.server.rest;
+package org.apache.directory.scim.server.exception;
 
 import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.WebApplicationException;
 import jakarta.ws.rs.core.HttpHeaders;
 import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.core.Response.Status;
 import jakarta.ws.rs.ext.ExceptionMapper;
 import jakarta.ws.rs.ext.Provider;
-
 import org.apache.directory.scim.protocol.Constants;
+import org.apache.directory.scim.protocol.ErrorMessageType;
 import org.apache.directory.scim.protocol.data.ErrorResponse;
+import org.apache.directory.scim.spec.exception.ResourceException;
 
 @Provider
 @Produces({Constants.SCIM_CONTENT_TYPE, MediaType.APPLICATION_JSON})
-public class WebApplicationExceptionMapper implements ExceptionMapper<WebApplicationException> {
+public class ResourceExceptionMapper implements ExceptionMapper<ResourceException> {
 
   @Override
-  public Response toResponse(WebApplicationException e) {
-    ErrorResponse em = new ErrorResponse(Status.fromStatusCode(e.getResponse().getStatus()), e.getMessage());
-
-    Response response = em.toResponse();
+  public Response toResponse(ResourceException e) {
+    Status status = Status.fromStatusCode(e.getStatus());
+    ErrorResponse errorResponse = new ErrorResponse(status, e.getMessage());
+
+    if (status == Status.CONFLICT) {
+      errorResponse.setScimType(ErrorMessageType.UNIQUENESS);
+
+      //only use default error message if the ErrorResponse does not already contain a message
+      if (e.getMessage() == null) {
+        errorResponse.setDetail(ErrorMessageType.UNIQUENESS.getDetail());
+      } else {
+        errorResponse.setDetail(e.getMessage());
+      }
+    } else {
+      errorResponse.setDetail(e.getMessage());
+    }
+
+    Response response = errorResponse.toResponse();
     response.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE, Constants.SCIM_CONTENT_TYPE);
 
     return response;
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/rest/WebApplicationExceptionMapper.java b/scim-server/src/main/java/org/apache/directory/scim/server/exception/ScimExceptionMapper.java
similarity index 75%
copy from scim-server/src/main/java/org/apache/directory/scim/server/rest/WebApplicationExceptionMapper.java
copy to scim-server/src/main/java/org/apache/directory/scim/server/exception/ScimExceptionMapper.java
index cd96d801..e8454237 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/rest/WebApplicationExceptionMapper.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/exception/ScimExceptionMapper.java
@@ -17,29 +17,27 @@
 * under the License.
 */
 
-package org.apache.directory.scim.server.rest;
+package org.apache.directory.scim.server.exception;
 
 import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.WebApplicationException;
 import jakarta.ws.rs.core.HttpHeaders;
 import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.Response;
-import jakarta.ws.rs.core.Response.Status;
 import jakarta.ws.rs.ext.ExceptionMapper;
 import jakarta.ws.rs.ext.Provider;
-
 import org.apache.directory.scim.protocol.Constants;
 import org.apache.directory.scim.protocol.data.ErrorResponse;
+import org.apache.directory.scim.protocol.exception.ScimException;
 
 @Provider
 @Produces({Constants.SCIM_CONTENT_TYPE, MediaType.APPLICATION_JSON})
-public class WebApplicationExceptionMapper implements ExceptionMapper<WebApplicationException> {
+public class ScimExceptionMapper implements ExceptionMapper<ScimException> {
 
   @Override
-  public Response toResponse(WebApplicationException e) {
-    ErrorResponse em = new ErrorResponse(Status.fromStatusCode(e.getResponse().getStatus()), e.getMessage());
+  public Response toResponse(ScimException e) {
+    ErrorResponse errorResponse = new ErrorResponse(e.getStatus(), e.getMessage());
 
-    Response response = em.toResponse();
+    Response response = errorResponse.toResponse();
     response.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE, Constants.SCIM_CONTENT_TYPE);
 
     return response;
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/exception/ScimServerException.java b/scim-server/src/main/java/org/apache/directory/scim/server/exception/ScimServerException.java
deleted file mode 100644
index af1804e0..00000000
--- a/scim-server/src/main/java/org/apache/directory/scim/server/exception/ScimServerException.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements.  See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership.  The ASF licenses this file
-* to you 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.apache.directory.scim.server.exception;
-
-import jakarta.ws.rs.core.Response.Status;
-
-import org.apache.directory.scim.protocol.ErrorMessageType;
-import org.apache.directory.scim.protocol.data.ErrorResponse;
-import lombok.Getter;
-
-public class ScimServerException extends Exception {
-
-  private static final long serialVersionUID = -3803568677019909403L;
-
-  @Getter
-  private final ErrorResponse errorResponse;
-
-  public ScimServerException(Status status, String detail) {
-    super(formatMessage(status, null, detail));
-    this.errorResponse = new ErrorResponse(status, detail);
-  }
-
-  public ScimServerException(Status status, String detail, Exception e) {
-    super(formatMessage(status, null, detail), e);
-    this.errorResponse = new ErrorResponse(status, detail);
-  }
-
-  public ScimServerException(Status status, ErrorMessageType errorMessageType, String detail) {
-    super(formatMessage(status, errorMessageType, detail));
-    this.errorResponse = new ErrorResponse(status, detail);
-    this.errorResponse.setScimType(errorMessageType);
-  }
-  
-  public ScimServerException(Status status, ErrorMessageType errorMessageType, String detail, Exception e) {
-    super(formatMessage(status, errorMessageType, detail), e);
-    this.errorResponse = new ErrorResponse(status, detail);
-    this.errorResponse.setScimType(errorMessageType);
-  }
-
-  private static String formatMessage(Status status, ErrorMessageType errorMessageType, String detail) {
-    return "Scim Error: " + status + (errorMessageType != null ? " (" + errorMessageType + ")" : "") + ", " + detail;
-  }
-
-}
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/exception/UnsupportedOperationExceptionMapper.java b/scim-server/src/main/java/org/apache/directory/scim/server/exception/UnsupportedOperationExceptionMapper.java
new file mode 100644
index 00000000..c3e71ba5
--- /dev/null
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/exception/UnsupportedOperationExceptionMapper.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.directory.scim.server.exception;
+
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+import org.apache.directory.scim.protocol.Constants;
+import org.apache.directory.scim.protocol.data.ErrorResponse;
+
+@Provider
+@Produces({Constants.SCIM_CONTENT_TYPE, MediaType.APPLICATION_JSON})
+public class UnsupportedOperationExceptionMapper implements ExceptionMapper<UnsupportedOperationException> {
+
+  @Override
+  public Response toResponse(UnsupportedOperationException throwable) {
+    ErrorResponse em = new ErrorResponse(Response.Status.NOT_IMPLEMENTED, throwable.getMessage());
+
+    Response response = em.toResponse();
+    response.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE, Constants.SCIM_CONTENT_TYPE);
+
+    return response;
+  }
+}
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/rest/WebApplicationExceptionMapper.java b/scim-server/src/main/java/org/apache/directory/scim/server/exception/WebApplicationExceptionMapper.java
similarity index 97%
rename from scim-server/src/main/java/org/apache/directory/scim/server/rest/WebApplicationExceptionMapper.java
rename to scim-server/src/main/java/org/apache/directory/scim/server/exception/WebApplicationExceptionMapper.java
index cd96d801..fb11df7e 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/rest/WebApplicationExceptionMapper.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/exception/WebApplicationExceptionMapper.java
@@ -17,7 +17,7 @@
 * under the License.
 */
 
-package org.apache.directory.scim.server.rest;
+package org.apache.directory.scim.server.exception;
 
 import jakarta.ws.rs.Produces;
 import jakarta.ws.rs.WebApplicationException;
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/rest/BaseResourceTypeResourceImpl.java b/scim-server/src/main/java/org/apache/directory/scim/server/rest/BaseResourceTypeResourceImpl.java
index 332aae1f..21ff4267 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/rest/BaseResourceTypeResourceImpl.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/rest/BaseResourceTypeResourceImpl.java
@@ -27,6 +27,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import java.util.function.Function;
 
 import jakarta.enterprise.inject.spi.CDI;
 import jakarta.ws.rs.WebApplicationException;
@@ -36,10 +37,12 @@ import jakarta.ws.rs.core.Response.ResponseBuilder;
 import jakarta.ws.rs.core.Response.Status;
 import jakarta.ws.rs.core.Response.Status.Family;
 
+import org.apache.directory.scim.protocol.exception.ScimException;
 import org.apache.directory.scim.server.exception.*;
 import org.apache.directory.scim.core.repository.RepositoryRegistry;
 import org.apache.directory.scim.core.repository.Repository;
 import org.apache.directory.scim.core.schema.SchemaRegistry;
+import org.apache.directory.scim.spec.exception.ResourceException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -53,7 +56,6 @@ import org.apache.directory.scim.spec.filter.attribute.ScimRequestContext;
 import org.apache.directory.scim.core.repository.extensions.ClientFilterException;
 import org.apache.directory.scim.protocol.adapter.FilterWrapper;
 import org.apache.directory.scim.protocol.BaseResourceTypeResource;
-import org.apache.directory.scim.protocol.ErrorMessageType;
 import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
 import org.apache.directory.scim.spec.filter.attribute.AttributeReferenceListWrapper;
 import org.apache.directory.scim.protocol.data.ErrorResponse;
@@ -98,118 +100,66 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
     return repositoryRegistry.getRepository(resourceClass);
   }
 
-  Repository<T> getRepositoryInternal() throws ScimServerException {
+  Repository<T> getRepositoryInternal() throws ScimException {
     Repository<T> repository = getRepository();
     if (repository == null) {
-      throw new ScimServerException(Status.INTERNAL_SERVER_ERROR, "Provider not defined");
+      throw new ScimException(Status.INTERNAL_SERVER_ERROR, "Provider not defined");
     }
     return repository;
   }
 
   @Override
-  public Response getById(String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) {
+  public Response getById(String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException, ResourceException {
     if (requestContext.getUriInfo().getQueryParameters().getFirst("filter") != null) {
-      return Response.status(Status.FORBIDDEN)
-                     .build();
+      return Response.status(Status.FORBIDDEN).build();
     }
 
-    try {
-      Repository<T> repository = getRepositoryInternal();
-
-      T resource = null;
-      try {
-        resource = repository.get(id);
-      } catch (UnableToRetrieveResourceException e2) {
-        Status status = Status.fromStatusCode(e2.getStatus());
-        if (status.getFamily()
-              .equals(Family.SERVER_ERROR)) {
-          return createGenericExceptionResponse(e2, status);
-        }
-      } catch (Exception e) {
-        log.error("Uncaught repository exception", e);
-
-        return handleException(e);
-      }
-
-      if (resource != null) {
-        EntityTag backingETag = null;
-        try {
-          backingETag = etagGenerator.generateEtag(resource);
-        } catch (JsonProcessingException | NoSuchAlgorithmException | UnsupportedEncodingException e1) {
-          return createETagErrorResponse();
-        }
-
-        ResponseBuilder evaluatePreconditionsResponse = requestContext.getRequest().evaluatePreconditions(backingETag);
-
-        if (evaluatePreconditionsResponse != null) {
-          return Response.status(Status.NOT_MODIFIED)
-                         .build();
-        }
-      }
-
-      Set<AttributeReference> attributeReferences = Optional.ofNullable(attributes)
-                                                            .map(wrapper -> wrapper.getAttributeReferences())
-                                                            .orElse(Collections.emptySet());
-      Set<AttributeReference> excludedAttributeReferences = Optional.ofNullable(excludedAttributes)
-                                                                    .map(wrapper -> wrapper.getAttributeReferences())
-                                                                    .orElse(Collections.emptySet());
-
-      if (!attributeReferences.isEmpty() && !excludedAttributeReferences.isEmpty()) {
-        return createAmbiguousAttributeParametersResponse();
-      }
+    Repository<T> repository = getRepositoryInternal();
 
-      if (resource == null) {
-        return createNotFoundResponse(id);
+    T resource = null;
+    try {
+      resource = repository.get(id);
+    } catch (UnableToRetrieveResourceException e2) {
+      Status status = Status.fromStatusCode(e2.getStatus());
+      if (status.getFamily().equals(Family.SERVER_ERROR)) {
+        throw e2;
       }
+    }
 
-      EntityTag etag = null;
-
-      try {
-        etag = etagGenerator.generateEtag(resource);
-      } catch (JsonProcessingException | NoSuchAlgorithmException | UnsupportedEncodingException e) {
-        return createETagErrorResponse();
-      }
+    if (resource != null) {
+      EntityTag backingETag = requireEtag(resource);
+      ResponseBuilder evaluatePreconditionsResponse = requestContext.getRequest().evaluatePreconditions(backingETag);
 
-      // Process Attributes
-      try {
-        resource = processFilterAttributeExtensions(repository, resource, attributeReferences, excludedAttributeReferences);
-      } catch (ClientFilterException e1) {
-        ErrorResponse er = new ErrorResponse(e1.getStatus(), e1.getMessage());
-        return er.toResponse();
+      if (evaluatePreconditionsResponse != null) {
+        return Response.status(Status.NOT_MODIFIED).build();
       }
+    }
 
-      try {
-        if (!excludedAttributeReferences.isEmpty()) {
-          resource = attributeUtil.setExcludedAttributesForDisplay(resource, excludedAttributeReferences);
-        } else {
-          resource = attributeUtil.setAttributesForDisplay(resource, attributeReferences);
-        }
+    Set<AttributeReference> attributeReferences = AttributeReferenceListWrapper.getAttributeReferences(attributes);
+    Set<AttributeReference> excludedAttributeReferences = AttributeReferenceListWrapper.getAttributeReferences(excludedAttributes);
+    validateAttributes(attributeReferences, excludedAttributeReferences);
 
-        return Response.ok()
-                       .entity(resource)
-                       .location(buildLocationTag(resource))
-                       .tag(etag)
-                       .build();
-      } catch (AttributeException e) {
-        return createAttributeProcessingErrorResponse(e);
-      }
-    } catch (ScimServerException sse) {
-      LOG.error("Error Processing SCIM Request", sse);
-      return sse.getErrorResponse()
-                .toResponse();
+    if (resource == null) {
+      throw notFoundException(id);
     }
 
+    EntityTag etag = requireEtag(resource);
+
+    // Process Attributes
+    resource = processFilterAttributeExtensions(repository, resource, attributeReferences, excludedAttributeReferences);
+    resource = attributesForDisplayThrowOnError(resource, attributeReferences, excludedAttributeReferences);
+    return Response.ok()
+                   .entity(resource)
+                   .location(buildLocationTag(resource))
+                   .tag(etag)
+                   .build();
   }
 
   @Override
-  public Response query(AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes, FilterWrapper filter, AttributeReference sortBy, SortOrder sortOrder, Integer startIndex, Integer count) {
+  public Response query(AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes, FilterWrapper filter, AttributeReference sortBy, SortOrder sortOrder, Integer startIndex, Integer count) throws ScimException, ResourceException {
     SearchRequest searchRequest = new SearchRequest();
-    searchRequest.setAttributes(Optional.ofNullable(attributes)
-                                        .map(wrapper -> wrapper.getAttributeReferences())
-                                        .orElse(Collections.emptySet()));
-    searchRequest.setExcludedAttributes(Optional.ofNullable(excludedAttributes)
-                                                .map(wrapper -> wrapper.getAttributeReferences())
-                                                .orElse(Collections.emptySet()));
+    searchRequest.setAttributes(AttributeReferenceListWrapper.getAttributeReferences(attributes));
+    searchRequest.setExcludedAttributes(AttributeReferenceListWrapper.getAttributeReferences(excludedAttributes));
 
     if (filter != null) {
       searchRequest.setFilter(filter.getFilter());
@@ -227,428 +177,143 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
   }
 
   @Override
-  public Response create(T resource, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) {
-    try {
-      Repository<T> repository = getRepositoryInternal();
+  public Response create(T resource, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException, ResourceException {
+    Repository<T> repository = getRepositoryInternal();
 
-      Set<AttributeReference> attributeReferences = Optional.ofNullable(attributes)
-                                                            .map(wrapper -> wrapper.getAttributeReferences())
-                                                            .orElse(Collections.emptySet());
-      Set<AttributeReference> excludedAttributeReferences = Optional.ofNullable(excludedAttributes)
-                                                                    .map(wrapper -> wrapper.getAttributeReferences())
-                                                                    .orElse(Collections.emptySet());
+    Set<AttributeReference> attributeReferences = AttributeReferenceListWrapper.getAttributeReferences(attributes);
+    Set<AttributeReference> excludedAttributeReferences = AttributeReferenceListWrapper.getAttributeReferences(excludedAttributes);
+    validateAttributes(attributeReferences, excludedAttributeReferences);
 
-      if (!attributeReferences.isEmpty() && !excludedAttributeReferences.isEmpty()) {
-        return createAmbiguousAttributeParametersResponse();
-      }
+    T created = repository.create(resource);
 
-      T created;
-      try {
-        created = repository.create(resource);
-      } catch (UnableToCreateResourceException e1) {
-        Status status = Status.fromStatusCode(e1.getStatus());
-        ErrorResponse er = new ErrorResponse(status, "Error");
-
-        if (status == Status.CONFLICT) {
-          er.setScimType(ErrorMessageType.UNIQUENESS);
-          
-          //only use default error message if the ErrorResponse does not already contain a message
-          if (e1.getMessage() == null) {
-            er.setDetail(ErrorMessageType.UNIQUENESS.getDetail());
-          } else {
-            er.setDetail(e1.getMessage());
-          }
-        } else {
-          er.setDetail(e1.getMessage());
-        }
-
-        return er.toResponse();
-      } catch (Exception e) {
-        log.error("Uncaught repository exception", e);
+    EntityTag etag = etag(created);
 
-        return handleException(e);
-      }
-
-      EntityTag etag = null;
-      try {
-        etag = etagGenerator.generateEtag(created);
-      } catch (JsonProcessingException | NoSuchAlgorithmException | UnsupportedEncodingException e) {
-        log.error("Failed to generate etag for newly created entity " + e.getMessage());
-      }
+    // Process Attributes
+    created = processFilterAttributeExtensions(repository, created, attributeReferences, excludedAttributeReferences);
 
-      // Process Attributes
-      try {
-        created = processFilterAttributeExtensions(repository, created, attributeReferences, excludedAttributeReferences);
-      } catch (ClientFilterException e1) {
-        ErrorResponse er = new ErrorResponse(e1.getStatus(), e1.getMessage());
-        return er.toResponse();
-      }
-
-      try {
-        if (!excludedAttributeReferences.isEmpty()) {
-          created = attributeUtil.setExcludedAttributesForDisplay(created, excludedAttributeReferences);
-        } else {
-          created = attributeUtil.setAttributesForDisplay(created, attributeReferences);
-        }
-      } catch (AttributeException e) {
-        if (etag == null) {
-          return Response.status(Status.CREATED)
-                         .location(buildLocationTag(created))
-                         .build();
-        } else {
-          Response.status(Status.CREATED)
-                  .location(buildLocationTag(created))
-                  .tag(etag)
-                  .build();
-        }
-      }
-
-      // TODO - Is this the right behavior?
-      if (etag == null) {
+    try {
+      created = attributesForDisplay(created, attributeReferences, excludedAttributeReferences);
+    } catch (AttributeException e) {
+        log.debug("Exception thrown while processing attributes", e);
         return Response.status(Status.CREATED)
-                       .location(buildLocationTag(created))
-                       .entity(created)
-                       .build();
-      }
-
-      return Response.status(Status.CREATED)
-                     .location(buildLocationTag(created))
-                     .tag(etag)
-                     .entity(created)
-                     .build();
-    } catch (ScimServerException sse) {
-      LOG.error("Error Processing SCIM Request", sse);
-      return sse.getErrorResponse()
-                .toResponse();
+                .location(buildLocationTag(created))
+                .tag(etag)
+                .build();
     }
+
+    return Response.status(Status.CREATED)
+                   .location(buildLocationTag(created))
+                   .tag(etag)
+                   .entity(created)
+                   .build();
   }
 
   @Override
-  public Response find(SearchRequest request) {
-    try {
-      Repository<T> repository = getRepositoryInternal();
-
-      Set<AttributeReference> attributeReferences = Optional.ofNullable(request.getAttributes())
-                                                            .orElse(Collections.emptySet());
-      Set<AttributeReference> excludedAttributeReferences = Optional.ofNullable(request.getExcludedAttributes())
-                                                                    .orElse(Collections.emptySet());
-      if (!attributeReferences.isEmpty() && !excludedAttributeReferences.isEmpty()) {
-        return createAmbiguousAttributeParametersResponse();
-      }
-
-      Filter filter = request.getFilter();
-      PageRequest pageRequest = request.getPageRequest();
-      SortRequest sortRequest = request.getSortRequest();
-
-      ListResponse<T> listResponse = new ListResponse<>();
-
-      FilterResponse<T> filterResp = null;
-      try {
-        filterResp = repository.find(filter, pageRequest, sortRequest);
-      } catch (UnableToRetrieveResourceException e1) {
-        log.info("Caught an UnableToRetrieveResourceException " + e1.getMessage() + " : " + e1.getStatus());
-        return createGenericExceptionResponse(e1, e1.getStatus());
-      } catch (Exception e) {
-        log.error("Uncaught repository exception", e);
-
-        return handleException(e);
-      }
-
-      // If no resources are found, we should still return a ListResponse with
-      // the totalResults set to 0;
-      // (https://tools.ietf.org/html/rfc7644#section-3.4.2)
-      if (filterResp == null || filterResp.getResources() == null || filterResp.getResources()
-                                                                               .isEmpty()) {
-        listResponse.setTotalResults(0);
-      } else {
-        log.info("Find returned " + filterResp.getResources()
-                                              .size());
-        listResponse.setItemsPerPage(filterResp.getResources()
-                                               .size());
-        listResponse.setStartIndex(1);
-        listResponse.setTotalResults(filterResp.getResources()
-                                               .size());
-
-        List<T> results = new ArrayList<>();
-
-        for (T resource : filterResp.getResources()) {
-          EntityTag etag = null;
-
-          try {
-            etag = etagGenerator.generateEtag(resource);
-          } catch (JsonProcessingException | NoSuchAlgorithmException | UnsupportedEncodingException e) {
-            return createETagErrorResponse();
-          }
-
-          // Process Attributes
-          try {
-            log.info("=== Calling processFilterAttributeExtensions");
-            resource = processFilterAttributeExtensions(repository, resource, attributeReferences, excludedAttributeReferences);
-          } catch (ClientFilterException e1) {
-            ErrorResponse er = new ErrorResponse(e1.getStatus(), e1.getMessage());
-            return er.toResponse();
-          }
-
-          try {
-            if (!excludedAttributeReferences.isEmpty()) {
-              resource = attributeUtil.setExcludedAttributesForDisplay(resource, excludedAttributeReferences);
-            } else {
-              resource = attributeUtil.setAttributesForDisplay(resource, attributeReferences);
-            }
-
-            results.add(resource);
-          } catch (AttributeException e) {
-            return createAttributeProcessingErrorResponse(e);
-          }
-        }
-
-        listResponse.setResources(results);
+  public Response find(SearchRequest request) throws ScimException, ResourceException {
+    Repository<T> repository = getRepositoryInternal();
+
+    Set<AttributeReference> attributeReferences = Optional.ofNullable(request.getAttributes())
+                                                          .orElse(Collections.emptySet());
+    Set<AttributeReference> excludedAttributeReferences = Optional.ofNullable(request.getExcludedAttributes())
+                                                                  .orElse(Collections.emptySet());
+    validateAttributes(attributeReferences, excludedAttributeReferences);
+
+    Filter filter = request.getFilter();
+    PageRequest pageRequest = request.getPageRequest();
+    SortRequest sortRequest = request.getSortRequest();
+
+    ListResponse<T> listResponse = new ListResponse<>();
+
+    FilterResponse<T> filterResp = repository.find(filter, pageRequest, sortRequest);
+
+    // If no resources are found, we should still return a ListResponse with
+    // the totalResults set to 0;
+    // (https://tools.ietf.org/html/rfc7644#section-3.4.2)
+    if (filterResp == null || filterResp.getResources() == null || filterResp.getResources()
+                                                                             .isEmpty()) {
+      listResponse.setTotalResults(0);
+    } else {
+      log.info("Find returned " + filterResp.getResources()
+                                            .size());
+      listResponse.setItemsPerPage(filterResp.getResources()
+                                             .size());
+      listResponse.setStartIndex(1);
+      listResponse.setTotalResults(filterResp.getResources()
+                                             .size());
+
+      List<T> results = new ArrayList<>();
+
+      for (T resource : filterResp.getResources()) {
+        EntityTag etag = requireEtag(resource);
+
+        // Process Attributes
+        resource = processFilterAttributeExtensions(repository, resource, attributeReferences, excludedAttributeReferences);
+        resource = attributesForDisplayThrowOnError(resource, attributeReferences, excludedAttributeReferences);
+        results.add(resource);
       }
 
-      return Response.ok()
-                     .entity(listResponse)
-                     .build();
-    } catch (ScimServerException sse) {
-      LOG.error("Error Processing SCIM Request", sse);
-      return sse.getErrorResponse()
-                .toResponse();
+      listResponse.setResources(results);
     }
+
+    return Response.ok()
+                   .entity(listResponse)
+                   .build();
   }
 
   @Override
-  public Response update(T resource, String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) {
-    try {
-      Repository<T> repository = getRepositoryInternal();
-
-      Set<AttributeReference> attributeReferences = Optional.ofNullable(attributes)
-                                                            .map(wrapper -> wrapper.getAttributeReferences())
-                                                            .orElse(Collections.emptySet());
-      Set<AttributeReference> excludedAttributeReferences = Optional.ofNullable(excludedAttributes)
-                                                                    .map(wrapper -> wrapper.getAttributeReferences())
-                                                                    .orElse(Collections.emptySet());
-
-      if (!attributeReferences.isEmpty() && !excludedAttributeReferences.isEmpty()) {
-        return createAmbiguousAttributeParametersResponse();
-      }
-
-      T stored;
-      try {
-        stored = repository.get(id);
-      } catch (UnableToRetrieveResourceException e2) {
-        log.error("Unable to retrieve resource with id: {}", id, e2);
-        return createGenericExceptionResponse(e2, e2.getStatus());
-      } catch (Exception e) {
-        log.error("Uncaught repository exception", e);
-
-        return handleException(e);
-      }
-
-      if (stored == null) {
-        return createNotFoundResponse(id);
-      }
-
-      EntityTag backingETag = null;
-      try {
-        backingETag = etagGenerator.generateEtag(stored);
-      } catch (JsonProcessingException | NoSuchAlgorithmException | UnsupportedEncodingException e1) {
-        return createETagErrorResponse();
-      }
-
-      ResponseBuilder evaluatePreconditionsResponse = requestContext.getRequest().evaluatePreconditions(backingETag);
-
-      if (evaluatePreconditionsResponse != null) {
-        return createPreconditionFailedResponse(id, evaluatePreconditionsResponse);
-      }
-
-      T updated;
-      try {
-        UpdateRequest<T> updateRequest = new UpdateRequest<>(id, stored, resource, schemaRegistry);
-        updated = repository.update(updateRequest);
-      } catch (UnableToUpdateResourceException e1) {
-        return createGenericExceptionResponse(e1, e1.getStatus());
-      } catch (Exception e1) {
-        log.error("Uncaught repository exception", e1);
-
-        return handleException(e1);
-      }
-
-      // Process Attributes
-      try {
-        updated = processFilterAttributeExtensions(repository, updated, attributeReferences, excludedAttributeReferences);
-      } catch (ClientFilterException e1) {
-        ErrorResponse er = new ErrorResponse(e1.getStatus(), e1.getMessage());
-        return er.toResponse();
-      }
-
-      try {
-        if (!excludedAttributeReferences.isEmpty()) {
-          updated = attributeUtil.setExcludedAttributesForDisplay(updated, excludedAttributeReferences);
-        } else {
-          updated = attributeUtil.setAttributesForDisplay(updated, attributeReferences);
-        }
-      } catch (AttributeException e) {
-        log.error("Failed to handle attribute processing in update " + e.getMessage());
-      }
-
-      EntityTag etag = null;
-      try {
-        etag = etagGenerator.generateEtag(updated);
-      } catch (JsonProcessingException | NoSuchAlgorithmException | UnsupportedEncodingException e) {
-        log.error("Failed to generate etag for newly created entity " + e.getMessage());
-      }
+  public Response update(T resource, String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException, ResourceException {
+    return update(id, attributes, excludedAttributes, (stored) ->
+      new UpdateRequest<>(id, stored, resource, schemaRegistry));
+  }
 
-      // TODO - Is this correct or should we support roll back semantics
-      if (etag == null) {
-        return Response.ok(updated)
-                       .location(buildLocationTag(updated))
-                       .build();
-      }
+  @Override
+  public Response patch(PatchRequest patchRequest, String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException, ResourceException {
 
-      return Response.ok(updated)
-                     .location(buildLocationTag(updated))
-                     .tag(etag)
-                     .build();
-    } catch (ScimServerException sse) {
-      LOG.error("Error Processing SCIM Request", sse);
-      return sse.getErrorResponse()
-                .toResponse();
-    }
+    return update(id, attributes, excludedAttributes, (stored) ->
+      new UpdateRequest<>(id, stored, patchRequest.getPatchOperationList(), schemaRegistry));
   }
 
   @Override
-  public Response patch(PatchRequest patchRequest, String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) {
-    try {
+  public Response delete(String id) throws ScimException, ResourceException {
       Repository<T> repository = getRepositoryInternal();
+      repository.delete(id);
+      return Response.noContent()
+        .build();
+  }
 
-      Set<AttributeReference> attributeReferences = Optional.ofNullable(attributes)
-                                                            .map(wrapper -> wrapper.getAttributeReferences())
-                                                            .orElse(Collections.emptySet());
-      Set<AttributeReference> excludedAttributeReferences = Optional.ofNullable(excludedAttributes)
-                                                                    .map(wrapper -> wrapper.getAttributeReferences())
-                                                                    .orElse(Collections.emptySet());
-
-      if (!attributeReferences.isEmpty() && !excludedAttributeReferences.isEmpty()) {
-        return createAmbiguousAttributeParametersResponse();
-      }
-
-      T stored;
-      try {
-        stored = repository.get(id);
-      } catch (UnableToRetrieveResourceException e2) {
-        log.error("Unable to retrieve resource with id: {}", id, e2);
-        return createGenericExceptionResponse(e2, e2.getStatus());
-      } catch (Exception e) {
-        log.error("Uncaught repository exception", e);
-
-        return handleException(e);
-      }
-
-      if (stored == null) {
-        return createNotFoundResponse(id);
-      }
-
-      EntityTag backingETag = null;
-      try {
-        backingETag = etagGenerator.generateEtag(stored);
-      } catch (JsonProcessingException | NoSuchAlgorithmException | UnsupportedEncodingException e1) {
-        return createETagErrorResponse();
-      }
-
-      ResponseBuilder evaluatePreconditionsResponse = requestContext.getRequest().evaluatePreconditions(backingETag);
-
-      if (evaluatePreconditionsResponse != null) {
-        return createPreconditionFailedResponse(id, evaluatePreconditionsResponse);
-      }
-
-      T updated;
-      try {
-        UpdateRequest<T> updateRequest = new UpdateRequest<>(id, stored, patchRequest.getPatchOperationList(), schemaRegistry);
-        updated = repository.update(updateRequest);
-      } catch (UnableToUpdateResourceException e1) {
-        return createGenericExceptionResponse(e1, e1.getStatus());
-      } catch (UnsupportedOperationException e2) {
-        return createGenericExceptionResponse(e2, Status.NOT_IMPLEMENTED);
-      } catch (Exception e1) {
-        log.error("Uncaught repository exception", e1);
-
-        return handleException(e1);
-      }
+  private Response update(String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes, Function<T, UpdateRequest<T>> updateRequestFunction) throws ScimException, ResourceException {
 
-      // Process Attributes
-      try {
-        updated = processFilterAttributeExtensions(repository, updated, attributeReferences, excludedAttributeReferences);
-      } catch (ClientFilterException e1) {
-        ErrorResponse er = new ErrorResponse(e1.getStatus(), e1.getMessage());
-        return er.toResponse();
-      }
+    Repository<T> repository = getRepositoryInternal();
 
-      try {
-        if (!excludedAttributeReferences.isEmpty()) {
-          updated = attributeUtil.setExcludedAttributesForDisplay(updated, excludedAttributeReferences);
-        } else {
-          updated = attributeUtil.setAttributesForDisplay(updated, attributeReferences);
-        }
-      } catch (AttributeException e) {
-        log.error("Failed to handle attribute processing in update " + e.getMessage());
-      }
+    Set<AttributeReference> attributeReferences = AttributeReferenceListWrapper.getAttributeReferences(attributes);
+    Set<AttributeReference> excludedAttributeReferences = AttributeReferenceListWrapper.getAttributeReferences(excludedAttributes);
+    validateAttributes(attributeReferences, excludedAttributeReferences);
 
-      EntityTag etag = null;
-      try {
-        etag = etagGenerator.generateEtag(updated);
-      } catch (JsonProcessingException | NoSuchAlgorithmException | UnsupportedEncodingException e) {
-        log.error("Failed to generate etag for newly created entity " + e.getMessage());
-      }
+    T stored = repository.get(id);
 
-      // TODO - Is this correct or should we support roll back semantics
-      if (etag == null) {
-        return Response.ok(updated)
-                       .location(buildLocationTag(updated))
-                       .build();
-      }
-
-      return Response.ok(updated)
-                     .location(buildLocationTag(updated))
-                     .tag(etag)
-                     .build();
-    } catch (ScimServerException sse) {
-      LOG.error("Error Processing SCIM Request", sse);
-      return sse.getErrorResponse()
-                .toResponse();
+    if (stored == null) {
+      throw notFoundException(id);
     }
 
-  }
+    EntityTag backingETag = requireEtag(stored);
+    validatePreconditions(id, backingETag);
 
-  @Override
-  public Response delete(String id) {
-    Response response;
-    try {
-      Repository<T> repository = getRepositoryInternal();
-
-      try {
-        response = Response.noContent()
-                           .build();
+    UpdateRequest<T> updateRequest = updateRequestFunction.apply(stored);
+    T updated = repository.update(updateRequest);
 
-        repository.delete(id);
-        return response;
-      } catch (UnableToDeleteResourceException e) {
-        response = Response.status(e.getStatus()).build();
-        log.error("Unable to delete resource", e);
+    // Process Attributes
+    updated = processFilterAttributeExtensions(repository, updated, attributeReferences, excludedAttributeReferences);
+    updated = attributesForDisplayIgnoreErrors(updated, attributeReferences, excludedAttributeReferences);
 
-        return response;
-      } catch (Exception e) {
-        log.error("Uncaught repository exception", e);
-
-        return handleException(e);
-      }
-    } catch (ScimServerException sse) {
-      LOG.error("Error Processing SCIM Request", sse);
-      return sse.getErrorResponse()
-                .toResponse();
-    }
+    EntityTag etag = etag(updated);
+    return Response.ok(updated)
+      .location(buildLocationTag(updated))
+      .tag(etag)
+      .build();
   }
 
   @SuppressWarnings("unchecked")
-  private T processFilterAttributeExtensions(Repository<T> repository, T resource, Set<AttributeReference> attributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ClientFilterException {
+  private T processFilterAttributeExtensions(Repository<T> repository, T resource, Set<AttributeReference> attributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ScimException {
     ScimProcessingExtension annotation = repository.getClass()
                                                  .getAnnotation(ScimProcessingExtension.class);
     if (annotation != null) {
@@ -659,8 +324,12 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
           AttributeFilterExtension attributeFilterExtension = (AttributeFilterExtension) processingExtension;
           ScimRequestContext scimRequestContext = new ScimRequestContext(attributeReferences, excludedAttributeReferences);
 
-          resource = (T) attributeFilterExtension.filterAttributes(resource, scimRequestContext);
-          log.info("Resource now - " + resource.toString());
+          try {
+            resource = (T) attributeFilterExtension.filterAttributes(resource, scimRequestContext);
+            log.debug("Resource now - " + resource.toString());
+          } catch (ClientFilterException e) {
+            throw new ScimException(Status.fromStatusCode(e.getStatus()), e.getMessage(), e);
+          }
         }
       }
     }
@@ -679,57 +348,69 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
                   .build();
   }
 
-  static Response createGenericExceptionResponse(Throwable e1, int statusCode) {
-    return createGenericExceptionResponse(e1, Status.fromStatusCode(statusCode));
-  }
-
-  public static Response createGenericExceptionResponse(Throwable e1, Status status) {
-    Status myStatus = status;
-    if (myStatus == null) {
-      myStatus = Status.INTERNAL_SERVER_ERROR;
+  private T attributesForDisplay(T resource, Set<AttributeReference> includedAttributes, Set<AttributeReference> excludedAttributes) throws AttributeException {
+    if (!excludedAttributes.isEmpty()) {
+      resource = attributeUtil.setExcludedAttributesForDisplay(resource, excludedAttributes);
+    } else {
+      resource = attributeUtil.setAttributesForDisplay(resource, includedAttributes);
     }
-
-    ErrorResponse er = new ErrorResponse(myStatus, e1 != null ? e1.getMessage() : "Unknown Server Error");
-    return er.toResponse();
+    return resource;
   }
 
-  private Response createAmbiguousAttributeParametersResponse() {
-    ErrorResponse er = new ErrorResponse(Status.BAD_REQUEST, "Cannot include both attributes and excluded attributes in a single request");
-    return er.toResponse();
+  private T attributesForDisplayIgnoreErrors(T resource, Set<AttributeReference> includedAttributes, Set<AttributeReference> excludedAttributes) {
+    try {
+      return attributesForDisplay(resource, includedAttributes, excludedAttributes);
+    } catch (AttributeException e) {
+      if (log.isDebugEnabled()) {
+        log.debug("Failed to handle attribute processing in update " + e.getMessage(), e);
+      } else {
+        log.warn("Failed to handle attribute processing in update " + e.getMessage());
+      }
+    }
+    return resource;
   }
 
-  private Response createNotFoundResponse(String id) {
-    ErrorResponse er = new ErrorResponse(Status.NOT_FOUND, "Resource " + id + " not found");
-    return er.toResponse();
+  private T attributesForDisplayThrowOnError(T resource, Set<AttributeReference> includedAttributes, Set<AttributeReference> excludedAttributes) throws ScimException {
+    try {
+      return attributesForDisplay(resource, includedAttributes, excludedAttributes);
+    } catch (AttributeException e) {
+      throw new ScimException(Status.INTERNAL_SERVER_ERROR, "Failed to parse the attribute query value " + e.getMessage(), e);
+    }
   }
 
-  private Response createETagErrorResponse() {
-    ErrorResponse er = new ErrorResponse(Status.INTERNAL_SERVER_ERROR, "Failed to generate the etag");
-    return er.toResponse();
+  private ScimException notFoundException(String id) {
+    return new ScimException(Status.NOT_FOUND, "Resource " + id + " not found");
   }
 
-  private Response createAttributeProcessingErrorResponse(Exception e) {
-    ErrorResponse er = new ErrorResponse(Status.INTERNAL_SERVER_ERROR, "Failed to parse the attribute query value " + e.getMessage());
-    return er.toResponse();
+  private void validatePreconditions(String id, EntityTag etag) {
+    ResponseBuilder response = requestContext.getRequest().evaluatePreconditions(etag);
+    if (response != null) {
+      throw new WebApplicationException(response
+        .entity(new ErrorResponse(Status.PRECONDITION_FAILED, "Failed to update record, backing record has changed - " + id))
+        .build());
+    }
   }
 
-  private Response createNoRepositoryException() {
-    ErrorResponse er = new ErrorResponse(Status.INTERNAL_SERVER_ERROR, "Repository not defined");
-    return er.toResponse();
+  private EntityTag requireEtag(ScimResource resource) throws ScimException {
+    try {
+      return etagGenerator.generateEtag(resource);
+    } catch (JsonProcessingException | NoSuchAlgorithmException | UnsupportedEncodingException e) {
+      throw new ScimException(Status.INTERNAL_SERVER_ERROR, "Failed to generate the etag", e);
+    }
   }
 
-  private Response createPreconditionFailedResponse(String id, ResponseBuilder evaluatePreconditionsResponse) {
-    ErrorResponse er = new ErrorResponse(Status.PRECONDITION_FAILED, "Failed to update record, backing record has changed - " + id);
-    log.warn("Failed to update record, backing record has changed - " + id);
-    return evaluatePreconditionsResponse.entity(er)
-                                        .build();
+  private EntityTag etag(ScimResource resource) {
+    try {
+      return etagGenerator.generateEtag(resource);
+    } catch (JsonProcessingException | NoSuchAlgorithmException | UnsupportedEncodingException e) {
+      log.warn("Failed to generate etag for resource", e);
+      return null;
+    }
   }
 
-  Response handleException(Throwable unhandled) {
-    // Allow for ErrorMessageViolationExceptionMapper to handle JAX-RS exceptions by default
-    if (unhandled instanceof WebApplicationException) {
-      throw (WebApplicationException) unhandled;
+  private void validateAttributes(Set<AttributeReference> attributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ScimException {
+    if (!attributeReferences.isEmpty() && !excludedAttributeReferences.isEmpty()) {
+      throw new ScimException(Status.BAD_REQUEST, "Cannot include both attributes and excluded attributes in a single request");
     }
-    return BaseResourceTypeResourceImpl.createGenericExceptionResponse(unhandled, Status.INTERNAL_SERVER_ERROR);
   }
 }
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/rest/ScimResourceHelper.java b/scim-server/src/main/java/org/apache/directory/scim/server/rest/ScimResourceHelper.java
index 26f52bfd..ee4e0b6b 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/rest/ScimResourceHelper.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/rest/ScimResourceHelper.java
@@ -21,7 +21,7 @@ package org.apache.directory.scim.server.rest;
 
 import java.util.Set;
 
-import org.apache.directory.scim.server.exception.FilterParseExceptionMapper;
+import org.apache.directory.scim.server.exception.*;
 
 /**
  * Provides the SCIM defined set of end-points and resources without declaring a
@@ -54,8 +54,14 @@ public final class ScimResourceHelper {
       SelfResourceImpl.class,
       ServiceProviderConfigResourceImpl.class,
       UserResourceImpl.class,
+
+      // exception mappers
+      ResourceExceptionMapper.class,
+      ScimExceptionMapper.class,
       FilterParseExceptionMapper.class,
       WebApplicationExceptionMapper.class,
+      UnsupportedOperationExceptionMapper.class,
+      GenericExceptionMapper.class,
 
     // handle MediaType of application/scim+json
     ScimJacksonXmlBindJsonProvider.class);
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/rest/SelfResourceImpl.java b/scim-server/src/main/java/org/apache/directory/scim/server/rest/SelfResourceImpl.java
index 32db8deb..cfabb667 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/rest/SelfResourceImpl.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/rest/SelfResourceImpl.java
@@ -61,15 +61,9 @@ public class SelfResourceImpl implements SelfResource {
   }
 
   @Override
-  public Response getSelf(AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) {
-    try {
-      String internalId = getInternalId();
-      return userResource.getById(internalId, attributes, excludedAttributes);
-    } catch (ResourceException e) {
-      return createErrorResponse(e);
-    } catch (ScimException e) {
-      return createErrorResponse(e);
-    }
+  public Response getSelf(AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException, ResourceException {
+    String internalId = getInternalId();
+    return userResource.getById(internalId, attributes, excludedAttributes);
   }
 
   // @Override
@@ -81,51 +75,21 @@ public class SelfResourceImpl implements SelfResource {
   // }
 
   @Override
-  public Response update(ScimUser resource, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) {
-    try {
-      String internalId = getInternalId();
-      return userResource.update(resource, internalId, attributes, excludedAttributes);
-    } catch (ResourceException e) {
-      return createErrorResponse(e);
-    } catch (ScimException e) {
-      return createErrorResponse(e);
-    }
+  public Response update(ScimUser resource, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException, ResourceException {
+    String internalId = getInternalId();
+    return userResource.update(resource, internalId, attributes, excludedAttributes);
   }
 
   @Override
-  public Response patch(PatchRequest patchRequest, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) {
-    try {
-      String internalId = getInternalId();
-      return userResource.patch(patchRequest, internalId, attributes, excludedAttributes);
-    } catch (ResourceException e) {
-      return createErrorResponse(e);
-    } catch (ScimException e) {
-      return createErrorResponse(e);
-    }
+  public Response patch(PatchRequest patchRequest, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException, ResourceException {
+    String internalId = getInternalId();
+    return userResource.patch(patchRequest, internalId, attributes, excludedAttributes);
   }
 
   @Override
-  public Response delete() {
-    try {
-      String internalId = getInternalId();
-      return userResource.delete(internalId);
-    } catch (ResourceException e) {
-      return createErrorResponse(e);
-    } catch (ScimException e) {
-      return createErrorResponse(e);
-    }
-  }
-
-  private Response createErrorResponse(ScimException e) {
-    ErrorResponse er = new ErrorResponse(e.getStatus(), "Error");
-    er.addErrorMessage(e.getMessage());
-    return er.toResponse();
-  }
-
-  private Response createErrorResponse(ResourceException e) {
-    ErrorResponse er = new ErrorResponse(e.getStatus(), "Error");
-    er.addErrorMessage(e.getMessage());
-    return er.toResponse();
+  public Response delete() throws ScimException, ResourceException {
+    String internalId = getInternalId();
+    return userResource.delete(internalId);
   }
 
   private String getInternalId() throws ResourceException {
diff --git a/scim-server/src/test/java/org/apache/directory/scim/server/rest/BaseResourceTypeResourceImplTest.java b/scim-server/src/test/java/org/apache/directory/scim/server/rest/BaseResourceTypeResourceImplTest.java
index f2996b62..5b2d04ec 100644
--- a/scim-server/src/test/java/org/apache/directory/scim/server/rest/BaseResourceTypeResourceImplTest.java
+++ b/scim-server/src/test/java/org/apache/directory/scim/server/rest/BaseResourceTypeResourceImplTest.java
@@ -19,32 +19,27 @@
 
 package org.apache.directory.scim.server.rest;
 
-import static com.googlecode.catchexception.CatchException.catchException;
-import static com.googlecode.catchexception.CatchException.caughtException;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.sameInstance;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
+import static org.hamcrest.Matchers.*;
+import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.Mockito.*;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-import jakarta.ws.rs.WebApplicationException;
 import jakarta.ws.rs.core.MultivaluedMap;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.core.Response.Status;
 
 import jakarta.ws.rs.core.UriInfo;
+import org.apache.directory.scim.protocol.exception.ScimException;
+import org.apache.directory.scim.spec.exception.ResourceException;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 
-import org.apache.directory.scim.server.exception.ScimServerException;
 import org.apache.directory.scim.core.repository.Repository;
 import org.apache.directory.scim.server.utility.ExampleObjectExtension;
 import org.apache.directory.scim.server.utility.ExampleObjectExtension.ComplexObject;
@@ -52,7 +47,6 @@ import org.apache.directory.scim.spec.extension.EnterpriseExtension;
 import org.apache.directory.scim.spec.extension.EnterpriseExtension.Manager;
 import org.apache.directory.scim.spec.phonenumber.PhoneNumberParseException;
 import org.apache.directory.scim.spec.filter.attribute.AttributeReferenceListWrapper;
-import org.apache.directory.scim.protocol.data.ErrorResponse;
 import org.apache.directory.scim.protocol.data.PatchRequest;
 import org.apache.directory.scim.protocol.data.SearchRequest;
 import org.apache.directory.scim.spec.resources.Address;
@@ -73,7 +67,7 @@ public class BaseResourceTypeResourceImplTest {
   AttributeReferenceListWrapper excludedAttributeList = new AttributeReferenceListWrapper("emails, phoneNumbers");
   
   @Test
-  public void testGetProviderInternal_ScimServerExceptionThrownWhenNoProvider() throws ScimServerException {
+  public void testGetProviderInternal_ScimServerExceptionThrownWhenNoProvider() throws ScimException {
     // given
     @SuppressWarnings("rawtypes")
     BaseResourceTypeResourceImpl baseResourceImpl = Mockito.mock(BaseResourceTypeResourceImpl.class);
@@ -81,12 +75,12 @@ public class BaseResourceTypeResourceImplTest {
     when(baseResourceImpl.getRepositoryInternal()).thenCallRealMethod();
     
     // when
-    assertThrows(ScimServerException.class, () -> baseResourceImpl.getRepositoryInternal());
+    assertThrows(ScimException.class, () -> baseResourceImpl.getRepositoryInternal());
   }
   
   @SuppressWarnings("unchecked")
   @Test
-  public void testGetById_ForbiddenIfNoFilter() {
+  public void testGetById_ForbiddenIfNoFilter() throws ScimException, ResourceException {
     // given
     @SuppressWarnings("rawtypes")
     BaseResourceTypeResourceImpl baseResourceImpl = Mockito.mock(BaseResourceTypeResourceImpl.class);
@@ -102,12 +96,12 @@ public class BaseResourceTypeResourceImplTest {
     Response response = baseResourceImpl.getById("1", includedAttributeList, excludedAttributeList);
     
     // then
-    assertTrue(response != null);
-    assertTrue(response.getStatus() == Status.FORBIDDEN.getStatusCode());
+    assertNotNull(response);
+    assertEquals(response.getStatus(), Status.FORBIDDEN.getStatusCode());
   }
   
   @Test
-  public void testQuery_NullParametersValid() {
+  public void testQuery_NullParametersValid() throws ScimException, ResourceException {
     // given
     @SuppressWarnings("rawtypes")
     BaseResourceTypeResourceImpl baseResourceImpl = Mockito.mock(BaseResourceTypeResourceImpl.class);
@@ -124,38 +118,31 @@ public class BaseResourceTypeResourceImplTest {
     Response response = baseResourceImpl.query(null, null, null, null, null, null, null);
     
     // then
-    verify(baseResourceImpl, times(1)).find(searchRequest);  
-    assertTrue(response != null);
-    assertTrue(response.getStatus() == Status.OK.getStatusCode());
+    verify(baseResourceImpl, times(1)).find(searchRequest);
+    assertNotNull(response);
+    assertEquals(response.getStatus(), Status.OK.getStatusCode());
   }
   
   @Test
-  public void testCreate_ErrorIfBothAttributesAndExcludedAttributesExist() {
+  public void testCreate_ErrorIfBothAttributesAndExcludedAttributesExist() throws ScimException, ResourceException, PhoneNumberParseException {
     // given
     @SuppressWarnings("unchecked")
     BaseResourceTypeResourceImpl<ScimUser> baseResourceImpl = Mockito.mock(BaseResourceTypeResourceImpl.class);
     
-    ScimUser scimUser = null;
-    try {
-      scimUser = getScimUser();
-    } catch (PhoneNumberParseException e) {
-      fail("Parsing phone number in getScimUser failed");
-    }
+    ScimUser scimUser = getScimUser();
     
     when(baseResourceImpl.create(scimUser, includedAttributeList, excludedAttributeList)).thenCallRealMethod();
     
     // when
-    Response response = baseResourceImpl.create(scimUser, includedAttributeList, excludedAttributeList);
-    
+    ScimException exception = assertThrows(ScimException.class, () -> baseResourceImpl.create(scimUser, includedAttributeList, excludedAttributeList));
+
     // then
-    assertTrue(response != null);
-    assertTrue(response.getStatus() == Status.BAD_REQUEST.getStatusCode());
-    assertTrue(response.getEntity() instanceof ErrorResponse);
-    assertTrue(((ErrorResponse)response.getEntity()).getDetail().equals("Cannot include both attributes and excluded attributes in a single request"));
+    assertEquals(exception.getStatus(), Status.BAD_REQUEST);
+    assertThat(exception.getError().getDetail(), is("Cannot include both attributes and excluded attributes in a single request"));
   }
   
   @Test
-  public void testFind_ErrorIfBothAttributesAndExcludedAttributesExist() {
+  public void testFind_ErrorIfBothAttributesAndExcludedAttributesExist() throws ScimException, ResourceException {
     // given
     @SuppressWarnings("rawtypes")
     BaseResourceTypeResourceImpl baseResourceImpl = Mockito.mock(BaseResourceTypeResourceImpl.class);
@@ -167,38 +154,29 @@ public class BaseResourceTypeResourceImplTest {
     when(baseResourceImpl.find(searchRequest)).thenCallRealMethod();
     
     // when
-    Response response = baseResourceImpl.find(searchRequest);
-    
+    ScimException exception = assertThrows(ScimException.class, () -> baseResourceImpl.find(searchRequest));
+
     // then
-    assertTrue(response != null);
-    assertTrue(response.getStatus() == Status.BAD_REQUEST.getStatusCode());
-    assertTrue(response.getEntity() instanceof ErrorResponse);
-    assertTrue(((ErrorResponse)response.getEntity()).getDetail().equals("Cannot include both attributes and excluded attributes in a single request"));
+    assertEquals(exception.getStatus(), Status.BAD_REQUEST);
+    assertThat(exception.getError().getDetail(), is("Cannot include both attributes and excluded attributes in a single request"));
   }
   
   @Test
-  public void testUpdate_ErrorIfBothAttributesAndExcludedAttributesExist() {
+  public void testUpdate_ErrorIfBothAttributesAndExcludedAttributesExist() throws ScimException, ResourceException, PhoneNumberParseException {
     // given
     @SuppressWarnings("unchecked")
     BaseResourceTypeResourceImpl<ScimUser> baseResourceImpl = Mockito.mock(BaseResourceTypeResourceImpl.class);
     
-    ScimUser scimUser = null;
-    try {
-      scimUser = getScimUser();
-    } catch (PhoneNumberParseException e) {
-      fail("Parsing phone number in getScimUser failed");
-    }
+    ScimUser scimUser = getScimUser();
     
     when(baseResourceImpl.update(scimUser, "1", includedAttributeList, excludedAttributeList)).thenCallRealMethod();
     
     // when
-    Response response = baseResourceImpl.update(scimUser, "1", includedAttributeList, excludedAttributeList);
-    
+    ScimException exception = assertThrows(ScimException.class, () -> baseResourceImpl.update(scimUser, "1", includedAttributeList, excludedAttributeList));
+
     // then
-    assertTrue(response != null);
-    assertTrue(response.getStatus() == Status.BAD_REQUEST.getStatusCode());
-    assertTrue(response.getEntity() instanceof ErrorResponse);
-    assertTrue(((ErrorResponse)response.getEntity()).getDetail().equals("Cannot include both attributes and excluded attributes in a single request"));
+    assertEquals(exception.getStatus(), Status.BAD_REQUEST);
+    assertThat(exception.getError().getDetail(), is("Cannot include both attributes and excluded attributes in a single request"));
   }
   
   @Test
@@ -212,46 +190,13 @@ public class BaseResourceTypeResourceImplTest {
     when(baseResourceImpl.patch(patchRequest, "1", includedAttributeList, excludedAttributeList)).thenCallRealMethod();
     
     // when
-    Response response = baseResourceImpl.patch(patchRequest, "1", includedAttributeList, excludedAttributeList);
-    
-    // then
-    assertTrue(response != null);
-    assertTrue(response.getStatus() == Status.BAD_REQUEST.getStatusCode());
-    assertTrue(response.getEntity() instanceof ErrorResponse);
-    assertTrue(((ErrorResponse)response.getEntity()).getDetail().equals("Cannot include both attributes and excluded attributes in a single request"));
-  }
-
-  @Test
-  public void handleException_jaxrsExceptionTest() {
-    BaseResourceTypeResourceImpl<ScimUser> baseResourceImpl = mock(BaseResourceTypeResourceImpl.class);
-    when(baseResourceImpl.handleException(any())).thenCallRealMethod();
-
-    Exception e = new WebApplicationException();
-    catchException(() -> baseResourceImpl.handleException(e));
-    assertThat(caughtException(), sameInstance(e));
-  }
-
-  @Test
-  public void handleException_runtimeExceptionTest() {
-    BaseResourceTypeResourceImpl<ScimUser> baseResourceImpl = mock(BaseResourceTypeResourceImpl.class);
-    when(baseResourceImpl.handleException(any())).thenCallRealMethod();
+    ScimException exception = assertThrows(ScimException.class, () -> baseResourceImpl.patch(patchRequest, "1", includedAttributeList, excludedAttributeList));
 
-    Exception e = new RuntimeException("fake test exception");
-    Response response = baseResourceImpl.handleException(e);
-    assertThat(response.getStatus(), is(500));
-    assertThat(((ErrorResponse)response.getEntity()).getDetail(), is("fake test exception"));
+    // then
+    assertEquals(exception.getStatus(), Status.BAD_REQUEST);
+    assertThat(exception.getError().getDetail(), is("Cannot include both attributes and excluded attributes in a single request"));
   }
 
-  @Test
-  public void handleException_nullExceptionTest() {
-    BaseResourceTypeResourceImpl<ScimUser> baseResourceImpl = mock(BaseResourceTypeResourceImpl.class);
-    when(baseResourceImpl.handleException(any())).thenCallRealMethod();
-
-    Response response = baseResourceImpl.handleException(null);
-    assertThat(response.getStatus(), is(500));
-    assertThat(((ErrorResponse)response.getEntity()).getDetail(), is("Unknown Server Error"));
-  }
-  
   private ScimUser getScimUser() throws PhoneNumberParseException {
     ScimUser user = new ScimUser();
 
diff --git a/scim-server/src/test/java/org/apache/directory/scim/server/rest/SelfResourceImplTest.java b/scim-server/src/test/java/org/apache/directory/scim/server/rest/SelfResourceImplTest.java
index 619c751b..982a59be 100644
--- a/scim-server/src/test/java/org/apache/directory/scim/server/rest/SelfResourceImplTest.java
+++ b/scim-server/src/test/java/org/apache/directory/scim/server/rest/SelfResourceImplTest.java
@@ -19,10 +19,8 @@
 package org.apache.directory.scim.server.rest;
 
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.hasItem;
-import static org.hamcrest.Matchers.hasSize;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.hamcrest.Matchers.sameInstance;
+import static org.hamcrest.Matchers.*;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -33,6 +31,7 @@ import jakarta.enterprise.inject.Instance;
 import jakarta.ws.rs.core.Response;
 
 import jakarta.ws.rs.core.SecurityContext;
+import org.apache.directory.scim.server.exception.UnableToResolveIdResourceException;
 import org.apache.directory.scim.spec.exception.ResourceException;
 import org.apache.directory.scim.core.repository.SelfIdResolver;
 import org.apache.directory.scim.protocol.UserResource;
@@ -56,11 +55,9 @@ public class SelfResourceImplTest {
 
     SelfResourceImpl selfResource = new SelfResourceImpl(null, selfIdResolverInstance, new RequestContext().setSecurityContext(securityContext));
 
-    Response response = selfResource.getSelf(null, null);
-    assertThat(response.getEntity(), instanceOf(ErrorResponse.class));
-    List<String> messages = ((ErrorResponse)response.getEntity()).getErrorMessageList();
-    assertThat(messages, hasItem("Caller SelfIdResolver not available"));
-    assertThat(messages, hasSize(1));
+    UnableToResolveIdResourceException exception = assertThrows(UnableToResolveIdResourceException.class, () -> selfResource.getSelf(null, null));
+
+    assertThat(exception.getMessage(), is("Caller SelfIdResolver not available"));
   }
 
   @Test
diff --git a/scim-spec/scim-spec-protocol/src/main/java/org/apache/directory/scim/protocol/BaseResourceTypeResource.java b/scim-spec/scim-spec-protocol/src/main/java/org/apache/directory/scim/protocol/BaseResourceTypeResource.java
index 2278ea11..806a8947 100644
--- a/scim-spec/scim-spec-protocol/src/main/java/org/apache/directory/scim/protocol/BaseResourceTypeResource.java
+++ b/scim-spec/scim-spec-protocol/src/main/java/org/apache/directory/scim/protocol/BaseResourceTypeResource.java
@@ -45,6 +45,7 @@ import jakarta.ws.rs.core.Response.Status;
 import org.apache.directory.scim.protocol.data.PatchRequest;
 import org.apache.directory.scim.protocol.exception.ScimException;
 import org.apache.directory.scim.protocol.adapter.FilterWrapper;
+import org.apache.directory.scim.spec.exception.ResourceException;
 import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
 import org.apache.directory.scim.spec.filter.attribute.AttributeReferenceListWrapper;
 import org.apache.directory.scim.protocol.data.SearchRequest;
@@ -76,7 +77,7 @@ public interface BaseResourceTypeResource<T> {
   })
     default Response getById(@Parameter(name="id", required=true) @PathParam("id") String id,
                              @Parameter(name="attributes") @QueryParam("attributes") AttributeReferenceListWrapper attributes,
-                             @Parameter(name="excludedAttributes") @QueryParam("excludedAttributes") AttributeReferenceListWrapper excludedAttributes) throws ScimException {
+                             @Parameter(name="excludedAttributes") @QueryParam("excludedAttributes") AttributeReferenceListWrapper excludedAttributes) throws ScimException, ResourceException {
     return Response.status(Status.NOT_IMPLEMENTED).build();
   }
 
@@ -102,7 +103,7 @@ public interface BaseResourceTypeResource<T> {
                          @Parameter(name="sortBy") @QueryParam("sortBy") AttributeReference sortBy,
                          @Parameter(name="sortOrder") @QueryParam("sortOrder") SortOrder sortOrder,
                          @Parameter(name="startIndex") @QueryParam("startIndex") Integer startIndex,
-                         @Parameter(name="count") @QueryParam("count") Integer count) throws ScimException {
+                         @Parameter(name="count") @QueryParam("count") Integer count) throws ScimException, ResourceException {
     return Response.status(Status.NOT_IMPLEMENTED).build();
   }
 
@@ -127,7 +128,7 @@ public interface BaseResourceTypeResource<T> {
                                        schema = @Schema(implementation = ScimResource.class)),
                                        required = true) T resource,
                           @Parameter(name="attributes") @QueryParam("attributes") AttributeReferenceListWrapper attributes,
-                          @Parameter(name="excludedAttributes") @QueryParam("excludedAttributes") AttributeReferenceListWrapper excludedAttributes) throws ScimException {
+                          @Parameter(name="excludedAttributes") @QueryParam("excludedAttributes") AttributeReferenceListWrapper excludedAttributes) throws ScimException, ResourceException {
     return Response.status(Status.NOT_IMPLEMENTED).build();
   }
 
@@ -148,7 +149,7 @@ public interface BaseResourceTypeResource<T> {
     @ApiResponse(responseCode = "501", description = "Not Implemented") })
   default Response find(@RequestBody(content = @Content(mediaType = Constants.SCIM_CONTENT_TYPE,
                                      schema = @Schema(implementation = SearchRequest.class)),
-                                     required = true) SearchRequest request) throws ScimException {
+                                     required = true) SearchRequest request) throws ScimException, ResourceException {
     return Response.status(Status.NOT_IMPLEMENTED).build();
   }
 
@@ -173,7 +174,7 @@ public interface BaseResourceTypeResource<T> {
                                        required = true) T resource,
                           @PathParam("id") String id,
                           @Parameter(name="attributes") @QueryParam("attributes") AttributeReferenceListWrapper attributes,
-                          @Parameter(name="excludedAttributes") @QueryParam("excludedAttributes") AttributeReferenceListWrapper excludedAttributes) throws ScimException {
+                          @Parameter(name="excludedAttributes") @QueryParam("excludedAttributes") AttributeReferenceListWrapper excludedAttributes) throws ScimException, ResourceException {
     return Response.status(Status.NOT_IMPLEMENTED).build();
   }
 
@@ -193,7 +194,7 @@ public interface BaseResourceTypeResource<T> {
                                       required = true) PatchRequest patchRequest,
                          @PathParam("id") String id,
                          @Parameter(name="attributes") @QueryParam("attributes") AttributeReferenceListWrapper attributes,
-                         @Parameter(name="excludedAttributes") @QueryParam("excludedAttributes") AttributeReferenceListWrapper excludedAttributes) throws ScimException {
+                         @Parameter(name="excludedAttributes") @QueryParam("excludedAttributes") AttributeReferenceListWrapper excludedAttributes) throws ScimException, ResourceException {
     return Response.status(Status.NOT_IMPLEMENTED).build();
   }
 
@@ -206,7 +207,7 @@ public interface BaseResourceTypeResource<T> {
     @ApiResponse(responseCode = "404", description = "Not found"),
     @ApiResponse(responseCode = "500", description = "Internal Server Error"),
     @ApiResponse(responseCode = "501", description = "Not Implemented") })
-  default Response delete(@Parameter(name = "id", required = true) @PathParam("id") String id) throws ScimException {
+  default Response delete(@Parameter(name = "id", required = true) @PathParam("id") String id) throws ScimException, ResourceException {
     return Response.status(Status.NOT_IMPLEMENTED).build();
   }
 }
diff --git a/scim-spec/scim-spec-protocol/src/main/java/org/apache/directory/scim/protocol/SelfResource.java b/scim-spec/scim-spec-protocol/src/main/java/org/apache/directory/scim/protocol/SelfResource.java
index 201fd443..3f415f9f 100644
--- a/scim-spec/scim-spec-protocol/src/main/java/org/apache/directory/scim/protocol/SelfResource.java
+++ b/scim-spec/scim-spec-protocol/src/main/java/org/apache/directory/scim/protocol/SelfResource.java
@@ -41,6 +41,8 @@ import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.core.Response.Status;
 
 import org.apache.directory.scim.protocol.data.PatchRequest;
+import org.apache.directory.scim.protocol.exception.ScimException;
+import org.apache.directory.scim.spec.exception.ResourceException;
 import org.apache.directory.scim.spec.filter.attribute.AttributeReferenceListWrapper;
 import org.apache.directory.scim.spec.resources.ScimResource;
 import org.apache.directory.scim.spec.resources.ScimUser;
@@ -91,7 +93,7 @@ public interface SelfResource {
     @ApiResponse(responseCode="501", description="Not Implemented")
   })
     default Response getSelf(@Parameter(name="attributes") @QueryParam("attributes") AttributeReferenceListWrapper attributes,
-                             @Parameter(name="excludedAttributes") @QueryParam("excludedAttributes") AttributeReferenceListWrapper excludedAttributes) throws Exception {
+                             @Parameter(name="excludedAttributes") @QueryParam("excludedAttributes") AttributeReferenceListWrapper excludedAttributes) throws ScimException, ResourceException {
     return Response.status(Status.NOT_IMPLEMENTED).build();
   }
 
@@ -116,7 +118,7 @@ public interface SelfResource {
                                        schema = @Schema(implementation = ScimResource.class)),
                                        required = true) ScimUser resource,
                           @Parameter(name="attributes") @QueryParam("attributes") AttributeReferenceListWrapper attributes,
-                          @Parameter(name="excludedAttributes") @QueryParam("excludedAttributes") AttributeReferenceListWrapper excludedAttributes) throws Exception {
+                          @Parameter(name="excludedAttributes") @QueryParam("excludedAttributes") AttributeReferenceListWrapper excludedAttributes) throws ScimException, ResourceException {
     return Response.status(Status.NOT_IMPLEMENTED).build();
   }
 
@@ -139,7 +141,7 @@ public interface SelfResource {
                                        schema = @Schema(implementation = ScimUser.class)),
                                        required = true) ScimUser resource,
                           @Parameter(name="attributes") @QueryParam("attributes") AttributeReferenceListWrapper attributes,
-                          @Parameter(name="excludedAttributes") @QueryParam("excludedAttributes") AttributeReferenceListWrapper excludedAttributes) throws Exception {
+                          @Parameter(name="excludedAttributes") @QueryParam("excludedAttributes") AttributeReferenceListWrapper excludedAttributes) throws ScimException, ResourceException {
     return Response.status(Status.NOT_IMPLEMENTED).build();
   }
 
@@ -159,7 +161,7 @@ public interface SelfResource {
                                       schema = @Schema(implementation = PatchRequest.class)),
                                       required = true) PatchRequest patchRequest,
                          @Parameter(name="attributes") @QueryParam("attributes") AttributeReferenceListWrapper attributes,
-                         @Parameter(name="excludedAttributes") @QueryParam("excludedAttributes") AttributeReferenceListWrapper excludedAttributes) throws Exception {
+                         @Parameter(name="excludedAttributes") @QueryParam("excludedAttributes") AttributeReferenceListWrapper excludedAttributes) throws ScimException, ResourceException {
     return Response.status(Status.NOT_IMPLEMENTED).build();
   }
 
@@ -171,7 +173,7 @@ public interface SelfResource {
     @ApiResponse(responseCode = "404", description = "Not found"),
     @ApiResponse(responseCode = "500", description = "Internal Server Error"),
     @ApiResponse(responseCode = "501", description = "Not Implemented") })
-  default Response delete() throws Exception {
+  default Response delete() throws ScimException, ResourceException {
     return Response.status(Status.NOT_IMPLEMENTED).build();
   }
 }
diff --git a/scim-spec/scim-spec-protocol/src/main/java/org/apache/directory/scim/protocol/exception/ScimException.java b/scim-spec/scim-spec-protocol/src/main/java/org/apache/directory/scim/protocol/exception/ScimException.java
index 48ce8829..63cec55a 100644
--- a/scim-spec/scim-spec-protocol/src/main/java/org/apache/directory/scim/protocol/exception/ScimException.java
+++ b/scim-spec/scim-spec-protocol/src/main/java/org/apache/directory/scim/protocol/exception/ScimException.java
@@ -33,6 +33,16 @@ public class ScimException extends Exception {
   private ErrorResponse error;
   private Status status;
 
+  public ScimException(Status status, String message, Throwable cause) {
+    super(message, cause);
+    this.error = new ErrorResponse(status, message);
+    this.status = status;
+  }
+
+  public ScimException(Status status, String message) {
+    this(new ErrorResponse(status, message), status);
+  }
+
   public ScimException(ErrorResponse error, Status status) {
     this.error = error;
     this.status = status;
diff --git a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/filter/attribute/AttributeReferenceListWrapper.java b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/filter/attribute/AttributeReferenceListWrapper.java
index f2819a6b..b2d39e8c 100644
--- a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/filter/attribute/AttributeReferenceListWrapper.java
+++ b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/filter/attribute/AttributeReferenceListWrapper.java
@@ -19,7 +19,9 @@
 
 package org.apache.directory.scim.spec.filter.attribute;
 
+import java.util.Collections;
 import java.util.HashSet;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -54,6 +56,12 @@ public class AttributeReferenceListWrapper {
     return wrapper;
   }
 
+  public static Set<AttributeReference> getAttributeReferences(AttributeReferenceListWrapper attributeReferenceListWrapper) {
+    return Optional.ofNullable(attributeReferenceListWrapper)
+      .map(wrapper -> wrapper.getAttributeReferences())
+      .orElse(Collections.emptySet());
+  }
+
   public String toString() {
     if (attributeReferences == null || attributeReferences.isEmpty()) {
       return "";