Remove parsing logic from ScimResource
Moves the parsing logic of extensions from ScimResource to a Jackson module
This should remove the need for a static ScimExtensionRegistry singleton
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/rest/AttributeUtil.java b/scim-server/src/main/java/org/apache/directory/scim/server/rest/AttributeUtil.java
index 7a4b541..a22a835 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/rest/AttributeUtil.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/rest/AttributeUtil.java
@@ -19,15 +19,11 @@
package org.apache.directory.scim.server.rest;
-import com.fasterxml.jackson.annotation.JsonInclude.Include;
-import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.module.SimpleModule;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.directory.scim.server.exception.AttributeDoesNotExistException;
import org.apache.directory.scim.server.exception.AttributeException;
-import org.apache.directory.scim.spec.json.ObjectMapperFactory;
import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
import org.apache.directory.scim.spec.resources.ScimExtension;
import org.apache.directory.scim.spec.resources.ScimGroup;
@@ -64,17 +60,9 @@
this.schemaRegistry = schemaRegistry;
// TODO move this to a CDI producer
- objectMapper = ObjectMapperFactory.getObjectMapper();
- objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
- objectMapper.setSerializationInclusion(Include.NON_NULL);
-
- SimpleModule module = new SimpleModule();
- module.addDeserializer(ScimResource.class, new ScimResourceDeserializer(this.schemaRegistry, this.objectMapper));
- objectMapper.registerModule(module);
+ objectMapper = new ObjectMapperFactory(schemaRegistry).createObjectMapper();
}
- AttributeUtil() {}
-
public <T extends ScimResource> T keepAlwaysAttributesForDisplay(T resource) throws AttributeException {
return setAttributesForDisplayInternal(resource, Returned.DEFAULT, Returned.REQUEST, Returned.NEVER);
}
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/rest/ObjectMapperFactory.java b/scim-server/src/main/java/org/apache/directory/scim/server/rest/ObjectMapperFactory.java
index 04d38fb..64bb88c 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/rest/ObjectMapperFactory.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/rest/ObjectMapperFactory.java
@@ -20,15 +20,20 @@
package org.apache.directory.scim.server.rest;
import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import com.fasterxml.jackson.databind.module.SimpleModule;
-import org.apache.directory.scim.spec.resources.ScimResource;
-
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Inject;
import jakarta.ws.rs.ext.Provider;
import org.apache.directory.scim.core.schema.SchemaRegistry;
+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 java.io.IOException;
/**
* Creates and configures an {@link ObjectMapper} used for {@code application/scim+json} parsing.
@@ -46,14 +51,46 @@
@Produces
public ObjectMapper createObjectMapper() {
- ObjectMapper objectMapper = org.apache.directory.scim.spec.json.ObjectMapperFactory.getObjectMapper();
+ ObjectMapper objectMapper = org.apache.directory.scim.spec.json.ObjectMapperFactory.getObjectMapper().copy();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
- SimpleModule module = new SimpleModule();
- module.addDeserializer(ScimResource.class, new ScimResourceDeserializer(schemaRegistry, objectMapper));
- objectMapper.registerModule(module);
-
+ objectMapper.registerModule(new ScimResourceModule(schemaRegistry));
return objectMapper;
}
+
+ static class ScimResourceModule extends SimpleModule {
+
+ public ScimResourceModule(SchemaRegistry schemaRegistry) {
+ super("scim-resources", Version.unknownVersion());
+ addDeserializer(ScimResource.class, new ScimResourceDeserializer(schemaRegistry));
+ }
+
+ @Override
+ public void setupModule(SetupContext context) {
+ super.setupModule(context);
+ context.addDeserializationProblemHandler(new UnknownPropertyHandler());
+ }
+ }
+
+ static class UnknownPropertyHandler extends DeserializationProblemHandler {
+ @Override
+ public boolean handleUnknownProperty(DeserializationContext ctxt, JsonParser p, JsonDeserializer<?> deserializer, Object beanOrClass, String propertyName) throws IOException {
+
+ if (beanOrClass instanceof ScimResource) {
+ ScimResource scimResource = (ScimResource) beanOrClass;
+ Class<? extends ScimResource> resourceClass = scimResource.getClass();
+ Class<? extends ScimExtension> extensionClass = ScimExtensionRegistry.getInstance().getExtensionClass(resourceClass, propertyName);
+
+ if (extensionClass != null) {
+ ScimExtension ext = ctxt.readPropertyValue(p, null, extensionClass);
+ if (ext != null) {
+ scimResource.addExtension(ext);
+ }
+ }
+ }
+ return super.handleUnknownProperty(ctxt, p, deserializer, beanOrClass, propertyName);
+ }
+ }
+
}
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/rest/ScimResourceDeserializer.java b/scim-server/src/main/java/org/apache/directory/scim/server/rest/ScimResourceDeserializer.java
index 13280ac..16de41d 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/rest/ScimResourceDeserializer.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/rest/ScimResourceDeserializer.java
@@ -19,52 +19,39 @@
package org.apache.directory.scim.server.rest;
-import java.io.IOException;
-
-import com.fasterxml.jackson.core.JsonLocation;
-import com.fasterxml.jackson.core.JsonParseException;
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.TreeNode;
+import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.node.ArrayNode;
-
-import org.apache.directory.scim.spec.resources.ScimResource;
import org.apache.directory.scim.core.schema.SchemaRegistry;
+import org.apache.directory.scim.spec.resources.ScimResource;
-public class ScimResourceDeserializer extends JsonDeserializer<ScimResource> {
+import java.io.IOException;
+import java.util.Objects;
+import java.util.stream.StreamSupport;
+
+public class ScimResourceDeserializer extends StdDeserializer<ScimResource> {
private final SchemaRegistry schemaRegistry;
- private final ObjectMapper objectMapper;
- public ScimResourceDeserializer(SchemaRegistry schemaRegistry, ObjectMapper objectMapper) {
+ public ScimResourceDeserializer(SchemaRegistry schemaRegistry) {
+ super(ScimResource.class);
this.schemaRegistry = schemaRegistry;
- this.objectMapper = objectMapper;
}
@Override
- public ScimResource deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
- ScimResource scimResource;
+ public ScimResource deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
JsonLocation location = jsonParser.getCurrentLocation();
TreeNode node = jsonParser.getCodec().readTree(jsonParser);
ArrayNode schemas = (ArrayNode) node.get("schemas");
- Class<? extends ScimResource> scimResourceClass = null;
- for (JsonNode schemaUrnNode : schemas) {
- String schemaUrn = schemaUrnNode.textValue();
- scimResourceClass = schemaRegistry.findScimResourceClass(schemaUrn);
+ Class<? extends ScimResource> scimResourceClass = StreamSupport.stream(schemas.spliterator(), false)
+ .map(JsonNode::textValue)
+ .map(schemaRegistry::findScimResourceClass)
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElseThrow(() -> new JsonParseException(jsonParser, "Could not find a valid schema in: " + schemas + ", valid schemas are: " + schemaRegistry.getAllSchemaUrns(), location));
- if (scimResourceClass != null) {
- break;
- }
- }
- if (scimResourceClass == null) {
- throw new JsonParseException("Could not find a valid schema in: " + schemas + ", valid schemas are: " + schemaRegistry.getAllSchemaUrns(), location);
- }
- scimResource = objectMapper.readValue(node.toString(), scimResourceClass);
-
- return scimResource;
+ return jsonParser.getCodec().treeToValue(node, scimResourceClass);
}
}
diff --git a/scim-server/src/test/java/org/apache/directory/scim/server/rest/ObjectMapperFactoryTest.java b/scim-server/src/test/java/org/apache/directory/scim/server/rest/ObjectMapperFactoryTest.java
new file mode 100644
index 0000000..d43afc8
--- /dev/null
+++ b/scim-server/src/test/java/org/apache/directory/scim/server/rest/ObjectMapperFactoryTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.rest;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.directory.scim.core.schema.SchemaRegistry;
+import org.apache.directory.scim.server.utility.ExampleObjectExtension;
+import org.apache.directory.scim.spec.extension.ScimExtensionRegistry;
+import org.apache.directory.scim.spec.resources.ScimResource;
+import org.apache.directory.scim.spec.resources.ScimUser;
+import org.apache.directory.scim.spec.schema.ResourceType;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+public class ObjectMapperFactoryTest {
+
+ @Test
+ public void serialize() throws JsonProcessingException {
+
+ ScimExtensionRegistry.getInstance().registerExtension(ScimUser.class, ExampleObjectExtension.class);
+
+ ResourceType userType = new ResourceType();
+ userType.setId("user");
+ userType.setSchemaUrn(ScimUser.SCHEMA_URI);
+ userType.setName(ScimUser.RESOURCE_NAME);
+ SchemaRegistry schemaRegistry = new SchemaRegistry();
+ schemaRegistry.addSchema(ScimUser.class, userType, List.of(ExampleObjectExtension.class));
+
+ ScimResource resource = new ScimUser().setId("test1");
+ ExampleObjectExtension extension = new ExampleObjectExtension().setValueDefault("test-value");
+ resource.addExtension(extension);
+
+ ObjectMapper objectMapper = new ObjectMapperFactory(schemaRegistry).createObjectMapper();
+ String json = objectMapper.writeValueAsString(resource);
+
+ ScimResource actual = objectMapper.readValue(json, ScimResource.class);
+
+ Assertions.assertThat(actual).isEqualTo(resource);
+ }
+}
diff --git a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/resources/ScimResource.java b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/resources/ScimResource.java
index c84d9ca..4d54c72 100644
--- a/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/resources/ScimResource.java
+++ b/scim-spec/scim-spec-schema/src/main/java/org/apache/directory/scim/spec/resources/ScimResource.java
@@ -20,7 +20,6 @@
package org.apache.directory.scim.spec.resources;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
-import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -28,7 +27,6 @@
import org.apache.directory.scim.spec.annotation.ScimExtensionType;
import org.apache.directory.scim.spec.annotation.ScimResourceType;
import org.apache.directory.scim.spec.exception.InvalidExtensionException;
-import org.apache.directory.scim.spec.extension.ScimExtensionRegistry;
import org.apache.directory.scim.spec.json.ObjectMapperFactory;
import org.apache.directory.scim.spec.schema.Meta;
import org.apache.directory.scim.spec.schema.Schema.Attribute.Returned;
@@ -99,7 +97,7 @@
public void addExtension(ScimExtension extension) {
ScimExtensionType[] se = extension.getClass().getAnnotationsByType(ScimExtensionType.class);
- if (se.length == 0 || se.length > 1) {
+ if (se.length != 1) {
throw new InvalidExtensionException("Registered extensions must have an ScimExtensionType annotation");
}
@@ -129,7 +127,7 @@
private <T> ScimExtensionType lookupScimExtensionType(Class<T> extensionClass) {
ScimExtensionType[] se = extensionClass.getAnnotationsByType(ScimExtensionType.class);
- if (se.length == 0 || se.length > 1) {
+ if (se.length != 1) {
throw new InvalidExtensionException("Registered extensions must have an ScimExtensionType annotation");
}
@@ -147,28 +145,6 @@
return extensions;
}
- @JsonAnySetter
- public void setExtensions(String key, Object value) {
- LOG.debug("Found a ScimExtension");
- LOG.debug("Extension's URN: " + key);
- LOG.debug("Extension's string representation: " + value);
-
- Class<? extends ScimResource> resourceClass = getClass();
- LOG.debug("Resource class: " + resourceClass.getSimpleName());
-
- Class<? extends ScimExtension> extensionClass = ScimExtensionRegistry.getInstance().getExtensionClass(resourceClass, key);
-
- if (extensionClass != null) {
- LOG.debug("Extension class: " + extensionClass.getSimpleName());
-
- ScimExtension extension = objectMapper.convertValue(value, extensionClass);
- if (extension != null) {
- LOG.debug(" ***** Added extension to the resource *****");
- extensions.put(key, extension);
- }
- }
- }
-
public ScimExtension removeExtension(String urn) {
return extensions.remove(urn);
}
@@ -179,5 +155,4 @@
return (T) extensions.remove(se.id());
}
-
}