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/15 20:17:19 UTC

[directory-scimple] 01/01: Move schema reg logic out of RepoReg and into SchemaReg

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

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

commit 3ecf17fb88a14f78fa7558f6503cb3b882c3905c
Author: Brian Demers <bd...@apache.org>
AuthorDate: Fri Sep 9 14:17:28 2022 -0400

    Move schema reg logic out of RepoReg and into SchemaReg
    
    Add a few tests
---
 .../scim/core/repository/RepositoryRegistry.java   |  26 ++----
 .../directory/scim/core/schema/SchemaRegistry.java |  55 ++++++-----
 .../core/repository/RepositoryRegistryTest.java    | 101 +++++++++++++++++++++
 .../scim/core/schema/SchemaRegistryTest.java       |  66 ++++++++++++++
 .../adapter/AttributeReferenceAdapterTest.java     |  74 +++++++++++++++
 .../scim/protocol/adapter/StatusAdapterTest.java   |  53 +++++++++++
 .../scim/spec/extension/ScimExtensionRegistry.java |  25 ++---
 .../apache/directory/scim/spec/schema/Schema.java  |  63 +++++++------
 8 files changed, 376 insertions(+), 87 deletions(-)

diff --git a/scim-core/src/main/java/org/apache/directory/scim/core/repository/RepositoryRegistry.java b/scim-core/src/main/java/org/apache/directory/scim/core/repository/RepositoryRegistry.java
index 19bf1490..f2db29f5 100644
--- a/scim-core/src/main/java/org/apache/directory/scim/core/repository/RepositoryRegistry.java
+++ b/scim-core/src/main/java/org/apache/directory/scim/core/repository/RepositoryRegistry.java
@@ -19,7 +19,6 @@
 
 package org.apache.directory.scim.core.repository;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.enterprise.inject.Instance;
 import jakarta.inject.Inject;
@@ -74,38 +73,27 @@ public class RepositoryRegistry implements Initializable {
       .forEach(repository -> {
       try {
         registerRepository(repository.getResourceClass(), repository);
-      } catch (InvalidRepositoryException | JsonProcessingException | ResourceException e) {
+      } catch (InvalidRepositoryException | ResourceException e) {
         throw new ScimResourceInvalidException("Failed to register repository " + repository.getClass() + " for ScimResource type " + repository.getResourceClass(), e);
       }
     });
   }
 
-  public synchronized <T extends ScimResource> void registerRepository(Class<T> clazz, Repository<T> repository) throws InvalidRepositoryException, JsonProcessingException, ResourceException {
+  public synchronized <T extends ScimResource> void registerRepository(Class<T> clazz, Repository<T> repository) throws InvalidRepositoryException, ResourceException {
 
     ResourceType resourceType = generateResourceType(clazz, repository);
 
-    log.info("Calling addSchema on the base class: {}", clazz);
-    schemaRegistry.addSchema(Schemas.schemaFor(clazz));
-    // NOTE generateResourceType() ensures ScimResourceType exists
-    ScimResourceType scimResourceType = clazz.getAnnotation(ScimResourceType.class);
-    String schemaUrn = scimResourceType.schema();
-    String endpoint = scimResourceType.endpoint();
-    schemaRegistry.addScimResourceSchemaUrn(schemaUrn, clazz);
-    schemaRegistry.addScimResourceEndPoint(endpoint, clazz);
-
     List<Class<? extends ScimExtension>> extensionList = repository.getExtensionList();
 
+    log.debug("Calling addSchema on the base class: {}", clazz);
+    schemaRegistry.addSchema(clazz, resourceType, extensionList);
+
     if (extensionList != null) {
       for (Class<? extends ScimExtension> scimExtension : extensionList) {
-        log.info("Registering a extension of type: " + scimExtension);
+        log.debug("Registering a extension of type: " + scimExtension);
         scimExtensionRegistry.registerExtension(clazz, scimExtension);
-        
-        log.info("Calling addSchema on an extension: " + scimExtension);
-        schemaRegistry.addSchema(Schemas.schemaForExtension(scimExtension));
       }
     }
-
-    schemaRegistry.addResourceType(resourceType);
     repositoryMap.put(clazz, repository);
   }
 
@@ -119,7 +107,7 @@ public class RepositoryRegistry implements Initializable {
     ScimResourceType scimResourceType = base.getAnnotation(ScimResourceType.class);
 
     if (scimResourceType == null) {
-      throw new InvalidRepositoryException("Missing annotation: ScimResourceType must be at the top of scim resource classes");
+      throw new InvalidRepositoryException("Missing annotation: @ScimResourceType must be at the top of scim resource classes");
     }
 
     ResourceType resourceType = new ResourceType();
diff --git a/scim-core/src/main/java/org/apache/directory/scim/core/schema/SchemaRegistry.java b/scim-core/src/main/java/org/apache/directory/scim/core/schema/SchemaRegistry.java
index bb573c05..37e5a0c5 100644
--- a/scim-core/src/main/java/org/apache/directory/scim/core/schema/SchemaRegistry.java
+++ b/scim-core/src/main/java/org/apache/directory/scim/core/schema/SchemaRegistry.java
@@ -19,20 +19,17 @@
 
 package org.apache.directory.scim.core.schema;
 
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.*;
 
 import jakarta.enterprise.context.ApplicationScoped;
+import org.apache.directory.scim.spec.annotation.ScimResourceType;
+import org.apache.directory.scim.spec.exception.ScimResourceInvalidException;
+import org.apache.directory.scim.spec.resources.ScimExtension;
 import org.apache.directory.scim.spec.resources.ScimResource;
 import org.apache.directory.scim.spec.schema.ResourceType;
 import org.apache.directory.scim.spec.schema.Schema;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.directory.scim.spec.schema.Schemas;
 
 @Slf4j
 @ApplicationScoped
@@ -46,8 +43,6 @@ public class SchemaRegistry {
 
   private Map<String, ResourceType> resourceTypeMap = new HashMap<>();
 
-  private ObjectMapper objectMapper;
-
   public Schema getSchema(String urn) {
     return schemaMap.get(urn);
   }
@@ -70,26 +65,39 @@ public class SchemaRegistry {
     return schemaMap.get(schemaUrn);
   }
 
-  public void addSchema(Schema schema) throws JsonProcessingException {
-    log.info("Adding schema " + schema.getId() + " into the registry");
+  private void addSchema(Schema schema) {
+    log.debug("Adding schema " + schema.getId() + " into the registry");
     schemaMap.put(schema.getId(), schema);
   }
 
-  public void addSchemaDoc(String schemaDoc) {
-    // Unmarshall the JSON document to a Schema and its associated object graph.
-    try {
-      Schema schema = objectMapper.readValue(schemaDoc, Schema.class);
-      schemaMap.put(schema.getId(), schema);
-    } catch (Throwable t) {
-      log.error("Unexpected Throwable was caught while unmarshalling JSON, schema will not be added: " + t.getLocalizedMessage());
+  public <T extends ScimResource> void addSchema(Class<T> clazz, ResourceType resourceType, List<Class<? extends ScimExtension>> extensionList) {
+
+    ScimResourceType scimResourceType = clazz.getAnnotation(ScimResourceType.class);
+    if (scimResourceType == null) {
+      throw new ScimResourceInvalidException("Missing annotation: ScimResource must be annotated with @ScimResourceType.");
+    }
+
+    String schemaUrn = scimResourceType.schema();
+    String endpoint = scimResourceType.endpoint();
+
+    addSchema(Schemas.schemaFor(clazz));
+    addScimResourceSchemaUrn(schemaUrn, clazz);
+    addScimResourceEndPoint(endpoint, clazz);
+    addResourceType(resourceType);
+
+    if (extensionList != null) {
+      for (Class<? extends ScimExtension> scimExtension : extensionList) {
+        log.debug("Calling addSchema on an extension: " + scimExtension);
+        addSchema(Schemas.schemaForExtension(scimExtension));
+      }
     }
   }
 
-  public <T extends ScimResource> void addScimResourceSchemaUrn(String schemaUrn, Class<T> scimResourceClass) {
+  private <T extends ScimResource> void addScimResourceSchemaUrn(String schemaUrn, Class<T> scimResourceClass) {
     schemaUrnToScimResourceClass.put(schemaUrn, scimResourceClass);
   }
 
-  public <T extends ScimResource> void addScimResourceEndPoint(String endpoint, Class<T> scimResourceClass) {
+  private <T extends ScimResource> void addScimResourceEndPoint(String endpoint, Class<T> scimResourceClass) {
     endpointToScimResourceClass.put(endpoint, scimResourceClass);
   }
 
@@ -114,9 +122,8 @@ public class SchemaRegistry {
   public Collection<ResourceType> getAllResourceTypes() {
     return Collections.unmodifiableCollection(resourceTypeMap.values());
   }
-  
-  public void addResourceType(ResourceType resourceType) {
+
+  private void addResourceType(ResourceType resourceType) {
     resourceTypeMap.put(resourceType.getName(), resourceType);
   }
-
 }
diff --git a/scim-core/src/test/java/org/apache/directory/scim/core/repository/RepositoryRegistryTest.java b/scim-core/src/test/java/org/apache/directory/scim/core/repository/RepositoryRegistryTest.java
new file mode 100644
index 00000000..f5e4f892
--- /dev/null
+++ b/scim-core/src/test/java/org/apache/directory/scim/core/repository/RepositoryRegistryTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.core.repository;
+
+import jakarta.enterprise.inject.Instance;
+import org.apache.directory.scim.core.schema.SchemaRegistry;
+import org.apache.directory.scim.spec.annotation.ScimExtensionType;
+import org.apache.directory.scim.spec.annotation.ScimResourceType;
+import org.apache.directory.scim.spec.exception.ResourceException;
+import org.apache.directory.scim.spec.exception.ScimResourceInvalidException;
+import org.apache.directory.scim.spec.extension.ScimExtensionRegistry;
+import org.apache.directory.scim.spec.resources.ScimExtension;
+import org.apache.directory.scim.spec.resources.ScimResource;
+import org.apache.directory.scim.spec.resources.ScimUser;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.*;
+
+public class RepositoryRegistryTest {
+
+  @Test
+  public void initializeWithException() throws InvalidRepositoryException, ResourceException {
+    SchemaRegistry schemaRegistry = new SchemaRegistry();
+    Instance<Repository<? extends ScimResource>> scimRepositoryInstances = mock(Instance.class);
+    Repository<ScimUser> repository = mock(Repository.class);
+    RepositoryRegistry repositoryRegistry = spy(new RepositoryRegistry(schemaRegistry, ScimExtensionRegistry.getInstance(), scimRepositoryInstances));
+
+    when(scimRepositoryInstances.stream()).thenReturn(Stream.of(repository));
+    doThrow(new InvalidRepositoryException("test exception")).when(repositoryRegistry).registerRepository(any(), any());
+
+    assertThrows(ScimResourceInvalidException.class, repositoryRegistry::initialize);
+  }
+
+  @Test
+  public void registerRepository() throws InvalidRepositoryException, ResourceException {
+    SchemaRegistry schemaRegistry = spy(new SchemaRegistry());
+    Instance<Repository<? extends ScimResource>> scimRepositoryInstances = mock(Instance.class);
+    Repository<StubResource> repository = mock(Repository.class);
+    ScimExtensionRegistry extensionRegistry = ScimExtensionRegistry.getInstance();
+    RepositoryRegistry repositoryRegistry = spy(new RepositoryRegistry(schemaRegistry, extensionRegistry, scimRepositoryInstances));
+
+    when(scimRepositoryInstances.stream()).thenReturn(Stream.of(repository));
+    when(repository.getExtensionList()).thenReturn(List.of(StubExtension.class));
+
+    // TODO: need to refactor out the ScimExtensionRegistry to make it easier for test
+    assertThat(extensionRegistry.getExtensionClass(StubResource.class, StubExtension.URN)).isNull();
+    repositoryRegistry.registerRepository(StubResource.class, repository);
+    assertThat(extensionRegistry.getExtensionClass(StubResource.class, StubExtension.URN)).isNotNull();
+
+    assertThat(repositoryRegistry.getRepository(StubResource.class)).isEqualTo(repository);
+  }
+
+  @ScimResourceType(id = StubResource.NAME, endpoint = "/Stub", schema = StubResource.URN)
+  static class StubResource extends ScimResource {
+
+    final static String URN = "urn:test:stub";
+    final static String NAME = "Stub";
+
+    public StubResource() {
+      super(URN);
+    }
+
+    @Override
+    public String getResourceType() {
+      return NAME;
+    }
+  }
+
+  @ScimExtensionType(name = StubExtension.NAME, id = StubExtension.URN)
+  static class StubExtension implements ScimExtension {
+    final static String URN = "urn:test:stub:extension";
+    final static String NAME = "StubExtension";
+
+    @Override
+    public String getUrn() {
+      return URN;
+    }
+  }
+}
diff --git a/scim-core/src/test/java/org/apache/directory/scim/core/schema/SchemaRegistryTest.java b/scim-core/src/test/java/org/apache/directory/scim/core/schema/SchemaRegistryTest.java
new file mode 100644
index 00000000..731ae1c2
--- /dev/null
+++ b/scim-core/src/test/java/org/apache/directory/scim/core/schema/SchemaRegistryTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.core.schema;
+
+import org.apache.directory.scim.core.repository.utility.ExampleObjectExtension;
+import org.apache.directory.scim.spec.resources.ScimGroup;
+import org.apache.directory.scim.spec.resources.ScimUser;
+import org.apache.directory.scim.spec.schema.ResourceType;
+import org.apache.directory.scim.spec.schema.Schema;
+import org.apache.directory.scim.spec.schema.Schemas;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SchemaRegistryTest {
+
+  @Test
+  public void registerSchema() {
+
+    SchemaRegistry schemaRegistry = new SchemaRegistry();
+    Schema userSchema = Schemas.schemaFor(ScimUser.class);
+    Schema groupsSchema = Schemas.schemaFor(ScimGroup.class);
+    Schema extSchema = Schemas.schemaForExtension(ExampleObjectExtension.class);
+
+    ResourceType userType = new ResourceType();
+    userType.setId("user");
+    userType.setSchemaUrn(ScimUser.SCHEMA_URI);
+    userType.setName(ScimUser.RESOURCE_NAME);
+    ResourceType groupType = new ResourceType();
+    groupType.setId("group");
+    groupType.setSchemaUrn(ScimGroup.SCHEMA_URI);
+    groupType.setName(ScimGroup.RESOURCE_NAME);
+
+    schemaRegistry.addSchema(ScimUser.class, userType, List.of(ExampleObjectExtension.class));
+    schemaRegistry.addSchema(ScimGroup.class, groupType, List.of());
+
+    assertThat(schemaRegistry.getSchema(ScimUser.SCHEMA_URI)).isEqualTo(userSchema);
+    assertThat(schemaRegistry.getAllSchemas()).containsOnly(userSchema, groupsSchema, extSchema);
+    assertThat(schemaRegistry.getAllSchemaUrns()).containsOnly(ScimUser.SCHEMA_URI, ScimGroup.SCHEMA_URI, ExampleObjectExtension.URN);
+    assertThat(schemaRegistry.getAllResourceTypes()).containsOnly(userType, groupType);
+    assertThat(schemaRegistry.getResourceType(ScimUser.RESOURCE_NAME)).isEqualTo(userType);
+    assertThat(schemaRegistry.findScimResourceClassFromEndpoint("/Users")).isEqualTo(ScimUser.class);
+    assertThat(schemaRegistry.findScimResourceClassFromEndpoint("/Groups")).isEqualTo(ScimGroup.class);
+    assertThat(schemaRegistry.findScimResourceClass(ScimUser.SCHEMA_URI)).isEqualTo(ScimUser.class);
+    assertThat(schemaRegistry.getBaseSchemaOfResourceType(ScimUser.RESOURCE_NAME)).isEqualTo(Schemas.schemaFor(ScimUser.class));
+  }
+}
diff --git a/scim-spec/scim-spec-protocol/src/test/java/org/apache/directory/scim/protocol/adapter/AttributeReferenceAdapterTest.java b/scim-spec/scim-spec-protocol/src/test/java/org/apache/directory/scim/protocol/adapter/AttributeReferenceAdapterTest.java
new file mode 100644
index 00000000..2bea5ba6
--- /dev/null
+++ b/scim-spec/scim-spec-protocol/src/test/java/org/apache/directory/scim/protocol/adapter/AttributeReferenceAdapterTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.protocol.adapter;
+
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AttributeReferenceAdapterTest {
+
+  private static Map<String, AttributeReference> TEST_DATA = Map.of(
+    "urn:test:testAttributeName.testSubAttributeName", new AttributeReference("urn:test", "testAttributeName", "testSubAttributeName"),
+    "urn:test:testAttributeName", new AttributeReference("urn:test", "testAttributeName"),
+    "testAttributeName", new AttributeReference("testAttributeName")
+  );
+
+  @ParameterizedTest
+  @MethodSource("marshalTestArgs")
+  public void marshal(AttributeReference input, String expected) throws Exception {
+    assertThat(new AttributeReferenceAdapter().marshal(input)).isEqualTo(expected);
+  }
+
+  @Test
+  public void marshalNull() throws Exception {
+    String result = new AttributeReferenceAdapter().marshal(null);
+    assertThat(result).isNull();
+  }
+
+  @ParameterizedTest
+  @MethodSource("unmarshalTestArgs")
+  public void unmarshal(String input, AttributeReference expected) throws Exception {
+    assertThat(new AttributeReferenceAdapter().unmarshal(input)).isEqualTo(expected);
+  }
+
+  @Test
+  public void unmarshalNull() throws Exception {
+    AttributeReference result = new AttributeReferenceAdapter().unmarshal(null);
+    assertThat(result).isNull();
+  }
+
+  private static Stream<Arguments> unmarshalTestArgs() {
+    return TEST_DATA.entrySet().stream()
+      .map(e -> Arguments.of(e.getKey(), e.getValue()));
+  }
+
+  private static Stream<Arguments> marshalTestArgs() {
+    return TEST_DATA.entrySet().stream()
+      .map(e -> Arguments.of(e.getValue(), e.getKey()));
+  }
+}
diff --git a/scim-spec/scim-spec-protocol/src/test/java/org/apache/directory/scim/protocol/adapter/StatusAdapterTest.java b/scim-spec/scim-spec-protocol/src/test/java/org/apache/directory/scim/protocol/adapter/StatusAdapterTest.java
new file mode 100644
index 00000000..0b4e2b13
--- /dev/null
+++ b/scim-spec/scim-spec-protocol/src/test/java/org/apache/directory/scim/protocol/adapter/StatusAdapterTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.protocol.adapter;
+
+import jakarta.ws.rs.core.Response.Status;
+import org.apache.directory.scim.protocol.data.StatusAdapter;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class StatusAdapterTest {
+
+  @Test
+  public void marshal() throws Exception {
+    String result = new StatusAdapter().marshal(Status.CONFLICT);
+    assertThat(result).isEqualTo("409");
+  }
+
+  @Test
+  public void marshalNull() throws Exception {
+    String result = new StatusAdapter().marshal(null);
+    assertThat(result).isNull();
+  }
+
+  @Test
+  public void unmarshal() throws Exception {
+    Status result = new StatusAdapter().unmarshal("400");
+    assertThat(result).isEqualTo(Status.BAD_REQUEST);
+  }
+
+  @Test
+  public void unmarshalNull() throws Exception {
+    Status result = new StatusAdapter().unmarshal(null);
+    assertThat(result).isNull();
+  }
+}
diff --git a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/extension/ScimExtensionRegistry.java b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/extension/ScimExtensionRegistry.java
index 94a622fa..2a272b4b 100644
--- a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/extension/ScimExtensionRegistry.java
+++ b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/extension/ScimExtensionRegistry.java
@@ -33,11 +33,9 @@ public final class ScimExtensionRegistry {
     
   private static final ScimExtensionRegistry INSTANCE = new ScimExtensionRegistry();
   
-  private Map<Class<? extends ScimResource>, Map<String, Class<? extends ScimExtension>>> registry;
+  private Map<Class<? extends ScimResource>, Map<String, Class<? extends ScimExtension>>> registry = new HashMap<>();
   
-  private ScimExtensionRegistry() {
-    registry = new HashMap<Class<? extends ScimResource>, Map<String, Class<? extends ScimExtension>>>();
-  }
+  private ScimExtensionRegistry() {}
   
   public Class<? extends ScimExtension> getExtensionClass(Class<? extends ScimResource> resourceClass, String urn) {
     Class<? extends ScimExtension> extensionClass = null;
@@ -57,22 +55,17 @@ public final class ScimExtensionRegistry {
   public void registerExtension(Class<? extends ScimResource> resourceClass, Class<? extends ScimExtension> extensionClass) {
     ScimExtensionType[] se = extensionClass.getAnnotationsByType(ScimExtensionType.class);
 
-    if (se.length == 0 || se.length > 1) {
-      throw new InvalidExtensionException("Registered extensions must have an ScimExtensionType annotation");
+    if (se.length != 1) {
+      throw new InvalidExtensionException("Registered extensions must a single @ScimExtensionType annotation");
     }
     
     String urn = se[0].id();
     
-    log.debug("Registering extension for URN: " + urn);
-    log.debug("    (associated resource class: " + resourceClass.getSimpleName() + ")");
-    log.debug("    (associated extension class: " + extensionClass.getSimpleName() + ")");
-    
-    Map<String, Class<? extends ScimExtension>> resourceMap = registry.get(resourceClass);
-    if(resourceMap == null) {
-      resourceMap = new HashMap<>();
-      registry.put(resourceClass, resourceMap);
-    }
-    
+    log.debug("Registering extension for URN: '{}' associated resource class: '{}' and extension class: '{}'",
+      urn, resourceClass.getSimpleName(), extensionClass.getSimpleName() );
+
+    Map<String, Class<? extends ScimExtension>> resourceMap = registry.computeIfAbsent(resourceClass, k -> new HashMap<>());
+
     if(!resourceMap.containsKey(urn)) {
       resourceMap.put(urn, extensionClass);
     }
diff --git a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/schema/Schema.java b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/schema/Schema.java
index e13852b5..499e80f7 100644
--- a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/schema/Schema.java
+++ b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/schema/Schema.java
@@ -23,10 +23,7 @@ import jakarta.validation.constraints.NotNull;
 import jakarta.validation.constraints.Pattern;
 import jakarta.validation.constraints.Size;
 import jakarta.xml.bind.annotation.*;
-import lombok.AccessLevel;
-import lombok.Data;
-import lombok.Getter;
-import lombok.Setter;
+import lombok.*;
 import org.apache.directory.scim.spec.exception.ScimResourceInvalidException;
 import org.apache.directory.scim.spec.validator.Urn;
 import org.slf4j.Logger;
@@ -252,32 +249,42 @@ public class Schema implements AttributeContainer {
     Class<?> getType();
 
     static AttributeAccessor forField(Field field) {
-      return new AttributeAccessor() {
-        @Override
-        public <T> T get(Object resource) {
-          try {
-            field.setAccessible(true);
-            return (T) field.get(resource);
-          } catch (IllegalAccessException e) {
-            throw new ScimResourceInvalidException("Schema definition is invalid", e);
-          }
-        }
+      return new FieldAttributeAccessor(field);
+    }
+  }
 
-        @Override
-        public void set(Object resource, Object value) {
-          try {
-            field.setAccessible(true);
-            field.set(resource, value);
-          } catch (IllegalAccessException e) {
-            throw new ScimResourceInvalidException("Schema definition is invalid", e);
-          }
-        }
+  @EqualsAndHashCode
+  private static class FieldAttributeAccessor implements AttributeAccessor {
 
-        @Override
-        public Class<?> getType() {
-          return field.getType();
-        }
-      };
+    private final Field field;
+
+    public FieldAttributeAccessor(Field field) {
+      this.field = field;
+    }
+
+    @Override
+    public <T> T get(Object resource) {
+      try {
+        field.setAccessible(true);
+        return (T) field.get(resource);
+      } catch (IllegalAccessException e) {
+        throw new ScimResourceInvalidException("Schema definition is invalid", e);
+      }
+    }
+
+    @Override
+    public void set(Object resource, Object value) {
+      try {
+        field.setAccessible(true);
+        field.set(resource, value);
+      } catch (IllegalAccessException e) {
+        throw new ScimResourceInvalidException("Schema definition is invalid", e);
+      }
+    }
+
+    @Override
+    public Class<?> getType() {
+      return field.getType();
     }
   }
 }