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 2023/12/01 16:09:59 UTC
(directory-scimple) branch develop updated: Move attribute and etag logic into Repository (#411)
This is an automated email from the ASF dual-hosted git repository.
bdemers pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/directory-scimple.git
The following commit(s) were added to refs/heads/develop by this push:
new 65e5f13c Move attribute and etag logic into Repository (#411)
65e5f13c is described below
commit 65e5f13c857dd031086957f20a701977b37b1734
Author: Brian Demers <bd...@apache.org>
AuthorDate: Fri Dec 1 11:09:55 2023 -0500
Move attribute and etag logic into Repository (#411)
* Move attribute and etag logic into Repository
Breaks up the current `update` method into new `update` and `patch` methods.
(Breaking change)
- ETag/Version logic must be managed by Repositories, except for the case of `ServiceProviderConfiguration` (which is likely stored in memory)
- Implementations should set `scimResource.meta.version`, how this version is defined is up to the implementation
- The `UpdateRequest` class has been removed, the `getPatchOperations` method has been moved into a new class `PatchGenerator`
- Current examples have been updated and demo simple in memory patch application. e.g. Get/apply patch/persist.
- Other implementations may want generate update queries directly from the list of PatchOperations (skipping intermediate get step)
* Inject PatchHandler into exmaple constructors
Rename PatchHandlerImpl to DefaultPatchHandler (users of SCIMple could create different implementations)
Update examples to inject PatchHandler
* Adding section in readme that maps HTTP requests to Repository methods.
NOTE: This section does not describe the supporting endpoints that SCIMple handles automatically
---
README.md | 26 +
...chHandlerImpl.java => DefaultPatchHandler.java} | 16 +-
.../scim/core/repository/PatchGenerator.java | 532 +++++++++++++++++++
.../directory/scim/core/repository/Repository.java | 39 +-
.../scim/core/repository/UpdateRequest.java | 585 ---------------------
...ateRequestTest.java => PatchGeneratorTest.java} | 124 ++---
.../scim/core/repository/PatchHandlerTest.java | 4 +-
.../jersey/service/InMemoryGroupService.java | 23 +-
.../jersey/service/InMemoryUserService.java | 27 +-
.../memory/service/InMemoryGroupService.java | 23 +-
.../memory/service/InMemoryUserService.java | 26 +-
.../quarkus/service/InMemoryGroupService.java | 23 +-
.../quarkus/service/InMemoryUserService.java | 30 +-
.../spring/service/InMemoryGroupService.java | 23 +-
.../spring/service/InMemoryUserService.java | 26 +-
.../server/rest/BaseResourceTypeResourceImpl.java | 102 ++--
.../scim/server/rest/BulkResourceImpl.java | 12 +-
.../scim/server/rest/GroupResourceImpl.java | 6 +-
.../scim/server/rest/UserResourceImpl.java | 6 +-
.../server/it/testapp/InMemoryGroupService.java | 28 +-
.../server/it/testapp/InMemoryUserService.java | 26 +-
.../scim/spring/ScimpleSpringConfiguration.java | 8 +
.../scim/spring/it/app/InMemoryGroupService.java | 23 +-
.../scim/spring/it/app/InMemoryUserService.java | 26 +-
24 files changed, 900 insertions(+), 864 deletions(-)
diff --git a/README.md b/README.md
index 7526bb63..14c664f5 100644
--- a/README.md
+++ b/README.md
@@ -114,6 +114,32 @@ public class InMemoryUserService implements Respository<ScimUser> {
Implementing the repository interface allows the customization of create, retrieve, update, and delete methods (as well as find). Customization is flexible - if your system implements soft deletes, create a delete method that simply sets a flag and alter the find and retrieve methods to only return "undeleted" resources.
+## Requests to Repository Mappings
+
+At a high level SCIMple provides the marshaling and unmarshaling of SCIM requests and responses to and from the appropriate repository methods and supporting endpoints (e.g. `/Schema`, `/ServiceProvider`, and `/ResourceTypes`). The following table shows the mapping of SCIM requests to repository methods:
+
+| SCIM Endpoint | HTTP Method | Repository Method |
+|---------------|---------------|-------------------------|
+| **Users** | | |
+| /Users | GET | `repository.find()` |
+| /Users | POST | `repository.create()` |
+| /Users/{id} | GET | `repository.get()` |
+| /Users/{id} | PUT | `repository.update()` |
+| /Users/{id} | PATCH | `repository.patch()` |
+| /Users/{id} | DELETE | `repository.delete()` |
+| **Groups** | | |
+| /Groups | GET | `repository.find()` |
+| /Groups | POST | `repository.create()` |
+| /Groups/{id} | GET | `repository.get()` |
+| /Groups/{id} | PUT | `repository.update()` |
+| /Groups/{id} | PATCH | `repository.patch()` |
+| /Groups/{id} | DELETE | `repository.delete()` |
+| **Other** | | |
+| /Bulk | POST | `repository.*` |
+
+> **NOTE:** The `/Bulk` endpoint enables clients to send a potentially large collection of resource operations
+in a single request, replacing the need to send multiple requests.
+
## Building & Contributing
Clone the code and build it!
diff --git a/scim-core/src/main/java/org/apache/directory/scim/core/repository/PatchHandlerImpl.java b/scim-core/src/main/java/org/apache/directory/scim/core/repository/DefaultPatchHandler.java
similarity index 98%
rename from scim-core/src/main/java/org/apache/directory/scim/core/repository/PatchHandlerImpl.java
rename to scim-core/src/main/java/org/apache/directory/scim/core/repository/DefaultPatchHandler.java
index ef341e41..f03d24a9 100644
--- a/scim-core/src/main/java/org/apache/directory/scim/core/repository/PatchHandlerImpl.java
+++ b/scim-core/src/main/java/org/apache/directory/scim/core/repository/DefaultPatchHandler.java
@@ -21,6 +21,8 @@ package org.apache.directory.scim.core.repository;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.apache.directory.scim.core.json.ObjectMapperFactory;
import org.apache.directory.scim.core.schema.SchemaRegistry;
@@ -49,12 +51,13 @@ import java.util.function.Predicate;
import static java.util.stream.Collectors.toList;
/**
- * The default implementation of a PatchHandler that applies PatchOperations by walking an map equivalent
+ * The default implementation of a PatchHandler that applies PatchOperations by walking a map equivalent
* of ScimResource.
*/
@SuppressWarnings("unchecked")
@Slf4j
-public class PatchHandlerImpl implements PatchHandler {
+@ApplicationScoped
+public class DefaultPatchHandler implements PatchHandler {
public static final String PRIMARY = "primary";
@@ -70,11 +73,18 @@ public class PatchHandlerImpl implements PatchHandler {
private final SchemaRegistry schemaRegistry;
- public PatchHandlerImpl(SchemaRegistry schemaRegistry) {
+ @Inject
+ public DefaultPatchHandler(SchemaRegistry schemaRegistry) {
this.schemaRegistry = schemaRegistry;
this.objectMapper = ObjectMapperFactory.createObjectMapper(this.schemaRegistry);
}
+ // For CDI
+ protected DefaultPatchHandler() {
+ this.objectMapper = null;
+ this.schemaRegistry = null;
+ }
+
@Override
public <T extends ScimResource> T apply(final T original, final List<PatchOperation> patchOperations) {
if (original == null) {
diff --git a/scim-core/src/main/java/org/apache/directory/scim/core/repository/PatchGenerator.java b/scim-core/src/main/java/org/apache/directory/scim/core/repository/PatchGenerator.java
new file mode 100644
index 00000000..c7945cf6
--- /dev/null
+++ b/scim-core/src/main/java/org/apache/directory/scim/core/repository/PatchGenerator.java
@@ -0,0 +1,532 @@
+/*
+* 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 com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.*;
+import com.flipkart.zjsonpatch.DiffFlags;
+import com.flipkart.zjsonpatch.JsonDiff;
+import org.apache.directory.scim.core.json.ObjectMapperFactory;
+import org.apache.directory.scim.core.schema.SchemaRegistry;
+import org.apache.directory.scim.spec.filter.AttributeComparisonExpression;
+import org.apache.directory.scim.spec.filter.CompareOperator;
+import org.apache.directory.scim.spec.filter.FilterExpression;
+import org.apache.directory.scim.spec.filter.ValuePathExpression;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
+import org.apache.directory.scim.spec.patch.PatchOperationPath;
+import org.apache.directory.scim.spec.resources.ScimExtension;
+import org.apache.directory.scim.spec.resources.ScimResource;
+import org.apache.directory.scim.spec.resources.TypedAttribute;
+import org.apache.directory.scim.spec.schema.AttributeContainer;
+import org.apache.directory.scim.spec.schema.Schema;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Generates a diff (i.e. a list of {@link PatchOperation}s) between two SCIM resources.
+ * This class could be used by SCIM clients when they only want to send PATCH requests over the wire (and communicate
+ * only the delta between the original and the modified resource).
+ * This class can also be used when testing SCIM servers as an easy way to generate a list of {@link PatchOperation}s.
+ */
+public class PatchGenerator {
+
+ private final SchemaRegistry schemaRegistry;
+
+ public PatchGenerator(SchemaRegistry schemaRegistry) {
+ this.schemaRegistry = schemaRegistry;
+ }
+
+ /**
+ * Returns a list of {@link PatchOperation}s that contain the differences between two {@link ScimResource}s.
+ * @param original an unmodified scim resource, as represented in a datastore.
+ * @param modified the modified version of the original scim resource.
+ * @return a list of {@link PatchOperation}s that contain the differences between two {@link ScimResource}s.
+ * @param <T> The type of the scim resource.
+ */
+ public <T extends ScimResource> List<PatchOperation> diff(T original, T modified) {
+ return new UpdateRequest<T>(original, modified, schemaRegistry).getPatchOperations();
+ }
+
+ static private class UpdateRequest<T extends ScimResource> {
+
+ private static final Logger log = LoggerFactory.getLogger(UpdateRequest.class);
+
+ private static final String OPERATION = "op";
+ private static final String PATH = "path";
+ private static final String VALUE = "value";
+
+ // Create a Jackson ObjectMapper that reads JaxB annotations
+ private final ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
+
+ private final T resource;
+ private final T original;
+
+ private final Schema schema;
+
+ private final SchemaRegistry schemaRegistry;
+
+ private final Map<Schema.Attribute, Integer> addRemoveOffsetMap = new HashMap<>();
+
+ private UpdateRequest(T original, T resource, SchemaRegistry schemaRegistry) {
+ this.schemaRegistry = schemaRegistry;
+ this.original = original;
+ this.resource = resource;
+ this.schema = schemaRegistry.getSchema(original.getBaseUrn());
+ }
+
+ public List<PatchOperation> getPatchOperations() {
+ try {
+ return createPatchOperations();
+ } catch (IllegalArgumentException | IllegalAccessException | JsonProcessingException e) {
+ throw new IllegalStateException("Error creating the patch list", e);
+ }
+ }
+
+ private void sortMultiValuedCollections(Object obj1, Object obj2, AttributeContainer ac) throws IllegalArgumentException {
+ for (Schema.Attribute attribute : ac.getAttributes()) {
+ Schema.AttributeAccessor accessor = attribute.getAccessor();
+ if (attribute.isMultiValued()) {
+ @SuppressWarnings("unchecked")
+ List<Object> collection1 = obj1 != null ? (List<Object>) accessor.get(obj1) : null;
+ @SuppressWarnings("unchecked")
+ List<Object> collection2 = obj2 != null ? (List<Object>) accessor.get(obj2) : null;
+
+ Set<Object> priorities = findCommonElements(collection1, collection2);
+ PrioritySortingComparator prioritySortingComparator = new PrioritySortingComparator(priorities);
+ if (collection1 != null) {
+ Collections.sort(collection1, prioritySortingComparator);
+ }
+
+ if (collection2 != null) {
+ Collections.sort(collection2, prioritySortingComparator);
+ }
+ } else if (attribute.getType() == Schema.Attribute.Type.COMPLEX) {
+ Object nextObj1 = obj1 != null ? accessor.get(obj1) : null;
+ Object nextObj2 = obj2 != null ? accessor.get(obj2) : null;
+ sortMultiValuedCollections(nextObj1, nextObj2, attribute);
+ }
+ }
+ }
+
+ private Set<Object> findCommonElements(List<Object> list1, List<Object> list2) {
+ if (list1 == null || list2 == null) {
+ return Collections.emptySet();
+ }
+
+ Set<Object> set1 = new HashSet<>(list1);
+ Set<Object> set2 = new HashSet<>(list2);
+
+ set1 = set1.stream().map(PrioritySortingComparator::getComparableValue).collect(Collectors.toSet());
+ set2 = set2.stream().map(PrioritySortingComparator::getComparableValue).collect(Collectors.toSet());
+
+ set1.retainAll(set2);
+ return set1;
+ }
+
+ /**
+ * There is a known issue with the diffing tool that the tool will attempt to move empty arrays. By
+ * nulling out the empty arrays during comparison, this will prevent that error from occurring because
+ * deleting requires the parent node.
+ * @param node Parent node.
+ */
+ private static void nullEmptyLists(JsonNode node) {
+ List<String> objectsToDelete = new ArrayList<>();
+
+ if (node != null) {
+ Iterator<Map.Entry<String, JsonNode>> children = node.fields();
+ while(children.hasNext()) {
+ Map.Entry<String, JsonNode> child = children.next();
+ String name = child.getKey();
+ JsonNode childNode = child.getValue();
+
+ //Attempt to delete children before analyzing
+ if (childNode.isContainerNode()) {
+ nullEmptyLists(childNode);
+ }
+
+ if (childNode instanceof ArrayNode) {
+ ArrayNode ar = (ArrayNode)childNode;
+ if (ar.size() == 0) {
+ objectsToDelete.add(name);
+ }
+ }
+ }
+
+ if (!objectsToDelete.isEmpty() && node instanceof ObjectNode) {
+ ObjectNode on = (ObjectNode)node;
+ for(String name : objectsToDelete) {
+ on.remove(name);
+ }
+ }
+ }
+ }
+
+ private List<PatchOperation> createPatchOperations() throws IllegalArgumentException, IllegalAccessException, JsonProcessingException {
+
+ sortMultiValuedCollections(this.original, this.resource, schema);
+ Map<String, ScimExtension> originalExtensions = this.original.getExtensions();
+ Map<String, ScimExtension> resourceExtensions = this.resource.getExtensions();
+ Set<String> keys = new HashSet<>();
+ keys.addAll(originalExtensions.keySet());
+ keys.addAll(resourceExtensions.keySet());
+
+ for(String key: keys) {
+ Schema extSchema = schemaRegistry.getSchema(key);
+ ScimExtension originalExtension = originalExtensions.get(key);
+ ScimExtension resourceExtension = resourceExtensions.get(key);
+ sortMultiValuedCollections(originalExtension, resourceExtension, extSchema);
+ }
+
+ JsonNode node1 = objectMapper.valueToTree(original);
+ nullEmptyLists(node1);
+ JsonNode node2 = objectMapper.valueToTree(resource);
+ nullEmptyLists(node2);
+ JsonNode differences = JsonDiff.asJson(node1, node2, DiffFlags.dontNormalizeOpIntoMoveAndCopy());
+
+ List<PatchOperation> patchOps = convertToPatchOperations(differences);
+ return patchOps;
+ }
+
+ List<PatchOperation> convertToPatchOperations(JsonNode node) throws IllegalArgumentException, IllegalAccessException, JsonProcessingException {
+ List<PatchOperation> operations = new ArrayList<>();
+ if (node == null) {
+ return Collections.emptyList();
+ }
+
+ if (!(node instanceof ArrayNode)) {
+ throw new RuntimeException("Expecting an instance of a ArrayNode, but got: " + node.getClass());
+ }
+
+ ArrayNode root = (ArrayNode) node;
+ for (int i = 0; i < root.size(); i++) {
+ ObjectNode patchNode = (ObjectNode) root.get(i);
+ JsonNode operationNode = patchNode.get(OPERATION);
+ JsonNode pathNode = patchNode.get(PATH);
+ JsonNode valueNode = patchNode.get(VALUE);
+
+ List<PatchOperation> nodeOperations = convertNodeToPatchOperations(operationNode.asText(), pathNode.asText(), valueNode);
+ if (!nodeOperations.isEmpty()) {
+ operations.addAll(nodeOperations);
+ }
+ }
+
+ return operations;
+ }
+
+ private List<PatchOperation> convertNodeToPatchOperations(String operationNode, String diffPath, JsonNode valueNode) throws IllegalArgumentException, IllegalAccessException, JsonProcessingException {
+ log.debug("convertNodeToPatchOperations: {} , {}", operationNode, diffPath);
+ List<PatchOperation> operations = new ArrayList<>();
+ PatchOperation.Type patchOpType = PatchOperation.Type.valueOf(operationNode.toUpperCase(Locale.ROOT));
+
+ if (diffPath != null && diffPath.length() >= 1) {
+ ParseData parseData = new ParseData(diffPath);
+
+ if (parseData.pathParts.isEmpty()) {
+ operations.add(handleExtensions(valueNode, patchOpType, parseData));
+ } else {
+ operations.addAll(handleAttributes(valueNode, patchOpType, parseData));
+ }
+ }
+
+ return operations;
+ }
+
+ private PatchOperation handleExtensions(JsonNode valueNode, PatchOperation.Type patchOpType, ParseData parseData) throws JsonProcessingException {
+ PatchOperation operation = new PatchOperation();
+ operation.setOperation(patchOpType);
+
+ AttributeReference attributeReference = new AttributeReference(parseData.pathUri, null);
+ PatchOperationPath patchOperationPath = new PatchOperationPath(new ValuePathExpression(attributeReference));
+
+ operation.setPath(patchOperationPath);
+ operation.setValue(determineValue(patchOpType, valueNode, parseData));
+
+ return operation;
+ }
+
+ @SuppressWarnings("unchecked")
+ private List<PatchOperation> handleAttributes(JsonNode valueNode, PatchOperation.Type patchOpType, ParseData parseData) throws IllegalAccessException, JsonProcessingException {
+ log.trace("in handleAttributes");
+ List<PatchOperation> operations = new ArrayList<>();
+
+ List<String> attributeReferenceList = new ArrayList<>();
+ FilterExpression valueFilterExpression = null;
+ List<String> subAttributes = new ArrayList<>();
+
+ boolean processingMultiValued = false;
+ boolean processedMultiValued = false;
+ boolean done = false;
+
+ int i = 0;
+ for (String pathPart : parseData.pathParts) {
+ log.trace("path part: {}", pathPart);
+ if (done) {
+ throw new RuntimeException("Path should be done... Attribute not supported by the schema: " + pathPart);
+ } else if (processingMultiValued) {
+ parseData.traverseObjectsInArray(pathPart, patchOpType);
+
+ if (!parseData.isLastIndex(i) || patchOpType != PatchOperation.Type.ADD) {
+ if (parseData.originalObject instanceof TypedAttribute) {
+ TypedAttribute typedAttribute = (TypedAttribute) parseData.originalObject;
+ String type = typedAttribute.getType();
+ valueFilterExpression = new AttributeComparisonExpression(new AttributeReference("type"), CompareOperator.EQ, type);
+ } else if (parseData.originalObject instanceof String || parseData.originalObject instanceof Number) {
+ String toString = parseData.originalObject.toString();
+ valueFilterExpression = new AttributeComparisonExpression(new AttributeReference("value"), CompareOperator.EQ, toString);
+ } else if(parseData.originalObject instanceof Enum) {
+ Enum<?> tempEnum = (Enum<?>)parseData.originalObject;
+ valueFilterExpression = new AttributeComparisonExpression(new AttributeReference("value"), CompareOperator.EQ, tempEnum.name());
+ } else {
+ if (parseData.originalObject != null) {
+ log.debug("Attribute: {} doesn't implement TypedAttribute, can't create ValueFilterExpression", parseData.originalObject.getClass());
+ } else {
+ log.debug("Attribute: null doesn't implement TypedAttribute, can't create ValueFilterExpression");
+ }
+ valueFilterExpression = new AttributeComparisonExpression(new AttributeReference("value"), CompareOperator.EQ, "?");
+ }
+ processingMultiValued = false;
+ processedMultiValued = true;
+ }
+ } else {
+ Schema.Attribute attribute = parseData.ac.getAttribute(pathPart);
+
+ if (attribute != null) {
+ if (processedMultiValued) {
+ subAttributes.add(pathPart);
+ } else {
+ log.debug("Adding {} to attributeReferenceList", pathPart);
+ attributeReferenceList.add(pathPart);
+ }
+
+ parseData.traverseObjects(pathPart, attribute);
+
+ if (patchOpType == PatchOperation.Type.REPLACE &&
+ (parseData.resourceObject != null && parseData.resourceObject instanceof Collection && !((Collection<?>)parseData.resourceObject).isEmpty()) &&
+ (parseData.originalObject == null ||
+ (parseData.originalObject instanceof Collection && ((Collection<?>)parseData.originalObject).isEmpty()))) {
+ patchOpType = PatchOperation.Type.ADD;
+ }
+
+ if (attribute.isMultiValued()) {
+ processingMultiValued = true;
+ } else if (attribute.getType() != Schema.Attribute.Type.COMPLEX) {
+ done = true;
+ }
+ }
+ }
+ ++i;
+ }
+
+ if (patchOpType == PatchOperation.Type.REPLACE && (parseData.resourceObject == null ||
+ (parseData.resourceObject instanceof Collection && ((Collection<?>)parseData.resourceObject).isEmpty()))) {
+ patchOpType = PatchOperation.Type.REMOVE;
+ valueNode = null;
+ }
+
+ if (patchOpType == PatchOperation.Type.REPLACE && parseData.originalObject == null) {
+ patchOpType = PatchOperation.Type.ADD;
+ }
+
+ if (!attributeReferenceList.isEmpty()) {
+ Object value = determineValue(patchOpType, valueNode, parseData);
+
+ if (value != null && value instanceof ArrayList) {
+ List<Object> objList = (List<Object>)value;
+
+ if (!objList.isEmpty()) {
+ Object firstElement = objList.get(0);
+ if (firstElement instanceof ArrayList) {
+ objList = (List<Object>) firstElement;
+ }
+
+ for (Object obj : objList) {
+ PatchOperation operation = buildPatchOperation(patchOpType, parseData, attributeReferenceList, valueFilterExpression, subAttributes, obj);
+ if (operation != null) {
+ operations.add(operation);
+ }
+ }
+ }
+ } else {
+ PatchOperation operation = buildPatchOperation(patchOpType, parseData, attributeReferenceList, valueFilterExpression, subAttributes, value);
+ if (operation != null) {
+ operations.add(operation);
+ }
+ }
+ }
+
+ return operations;
+ }
+
+ private PatchOperation buildPatchOperation(PatchOperation.Type patchOpType, ParseData parseData, List<String> attributeReferenceList,
+ FilterExpression valueFilterExpression, List<String> subAttributes, Object value) {
+ PatchOperation operation = new PatchOperation();
+ operation.setOperation(patchOpType);
+ String attribute = attributeReferenceList.get(0);
+ String subAttribute = attributeReferenceList.size() > 1 ? attributeReferenceList.get(1) : null;
+
+ if (subAttribute == null && !subAttributes.isEmpty()) {
+ subAttribute = subAttributes.get(0);
+ }
+ AttributeReference attributeReference = new AttributeReference(parseData.pathUri, attribute, subAttribute);
+ PatchOperationPath patchOperationPath = new PatchOperationPath(new ValuePathExpression(attributeReference, valueFilterExpression));
+
+ operation.setPath(patchOperationPath);
+ operation.setValue(value);
+
+ return operation;
+ }
+
+ private Object determineValue(PatchOperation.Type patchOpType, JsonNode valueNode, ParseData parseData) throws JsonProcessingException {
+ if (patchOpType == PatchOperation.Type.REMOVE) {
+ return null;
+ }
+
+ if (valueNode != null) {
+ if (valueNode instanceof TextNode) {
+ return valueNode.asText();
+ } else if (valueNode instanceof BooleanNode) {
+ return valueNode.asBoolean();
+ } else if (valueNode instanceof DoubleNode || valueNode instanceof FloatNode) {
+ return valueNode.asDouble();
+ } else if (valueNode instanceof IntNode) {
+ return valueNode.asInt();
+ } else if (valueNode instanceof NullNode) {
+ return null;
+ } else if (valueNode instanceof ObjectNode) {
+ return parseData.resourceObject;
+ } else if (valueNode instanceof POJONode) {
+ POJONode pojoNode = (POJONode) valueNode;
+ return pojoNode.getPojo();
+ } else if (valueNode instanceof ArrayNode) {
+ ArrayNode arrayNode = (ArrayNode) valueNode;
+ List<Object> objectList = new ArrayList<>();
+ for(int i = 0; i < arrayNode.size(); i++) {
+ Object subObject = determineValue(patchOpType, arrayNode.get(i), parseData);
+ if (subObject != null) {
+ objectList.add(subObject);
+ }
+ }
+ return objectList;
+ }
+ }
+ return null;
+ }
+
+ private class ParseData {
+
+ List<String> pathParts;
+ Object originalObject;
+ Object resourceObject;
+ AttributeContainer ac;
+ String pathUri;
+
+ public ParseData(String diffPath) {
+ String path = diffPath.substring(1);
+ pathParts = new ArrayList<>(Arrays.asList(path.split("/")));
+
+ // Extract namespace
+ pathUri = null;
+
+ String firstPathPart = pathParts.get(0);
+ if (firstPathPart.contains(":")) {
+ pathUri = firstPathPart;
+ pathParts.remove(0);
+ }
+
+ if (pathUri != null) {
+ ac = schemaRegistry.getSchema(pathUri);
+ originalObject = original.getExtension(pathUri);
+ resourceObject = resource.getExtension(pathUri);
+ } else {
+ ac = schema;
+ originalObject = original;
+ resourceObject = resource;
+ }
+ }
+
+ public void traverseObjects(String pathPart, Schema.Attribute attribute) throws IllegalArgumentException, IllegalAccessException {
+ originalObject = lookupAttribute(originalObject, ac, pathPart);
+ resourceObject = lookupAttribute(resourceObject, ac, pathPart);
+ ac = attribute;
+ }
+
+ public void traverseObjectsInArray(String pathPart, PatchOperation.Type patchOpType) {
+ int index = Integer.parseInt(pathPart);
+
+ Schema.Attribute attr = (Schema.Attribute) ac;
+
+ Integer addRemoveOffset = addRemoveOffsetMap.getOrDefault(attr, 0);
+ switch (patchOpType) {
+ case ADD:
+ addRemoveOffsetMap.put(attr, addRemoveOffset - 1);
+ break;
+ case REMOVE:
+ addRemoveOffsetMap.put(attr, addRemoveOffset + 1);
+ break;
+ case REPLACE:
+ default:
+ // Do Nothing
+ break;
+ }
+
+ int newindex = index + addRemoveOffset;
+ if (newindex < 0) {
+ log.error("Attempting to retrieve a negative index:{} on pathPath: {}", newindex, pathPart);
+ }
+
+ originalObject = lookupIndexInArray(originalObject, newindex);
+ resourceObject = lookupIndexInArray(resourceObject, index);
+ }
+
+ public boolean isLastIndex(int index) {
+ int numPathParts = pathParts.size();
+ return index == (numPathParts - 1);
+ }
+
+ @SuppressWarnings("rawtypes")
+ private Object lookupIndexInArray(Object object, int index) {
+ if (!(object instanceof List)) {
+ throw new RuntimeException("Unsupported collection type: " + object.getClass());
+ }
+ List list = (List) object;
+ if (index >= list.size()) {
+ return null;
+ }
+
+ return list.get(index);
+ }
+
+ private Object lookupAttribute(Object object, AttributeContainer ac, String attributeName) throws IllegalArgumentException, IllegalAccessException {
+ if (object == null) {
+ return null;
+ }
+
+ Schema.Attribute attribute = ac.getAttribute(attributeName);
+ return attribute.getAccessor().get(object);
+ }
+ }
+ }
+}
diff --git a/scim-core/src/main/java/org/apache/directory/scim/core/repository/Repository.java b/scim-core/src/main/java/org/apache/directory/scim/core/repository/Repository.java
index 1e59830a..3cbbc42d 100644
--- a/scim-core/src/main/java/org/apache/directory/scim/core/repository/Repository.java
+++ b/scim-core/src/main/java/org/apache/directory/scim/core/repository/Repository.java
@@ -21,12 +21,15 @@ package org.apache.directory.scim.core.repository;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
import org.apache.directory.scim.spec.exception.ResourceException;
import org.apache.directory.scim.spec.filter.FilterResponse;
import org.apache.directory.scim.spec.filter.Filter;
import org.apache.directory.scim.spec.filter.PageRequest;
import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
import org.apache.directory.scim.spec.resources.ScimExtension;
import org.apache.directory.scim.spec.resources.ScimResource;
@@ -60,14 +63,38 @@ public interface Repository<T extends ScimResource> {
/**
* Allows the SCIM server's REST implementation to update and existing
* resource via a PUT to a valid end-point.
- *
- * @param updateRequest The ScimResource to update and persist.
+ * <br>
+ * <b>SCIM Implementation NOTE:</b> SCIM supports <a href="https://datatracker.ietf.org/doc/html/rfc7644#section-3.14">versioning of resources via HTTP ETags</a>, if the (optional) {@code version} parameter is present, (and supported by the server),
+ * it can be used as a mechanism for caching and to ensure clients do not inadvertently overwrite other changes.
+ *
+ *
+ * @param id the identifier of the ScimResource to update and persist.
+ * @param version an optional version (usually used as an ETag) that can be used to optimize update requests, may be compared against, the current {@code ScimResource.meta.version}.
+ * @param resource an updated resource to persist
+ * @param includedAttributes optional set of attributes to include from ScimResource, may be used to optimize queries.
+ * @param excludedAttributes optional set of attributes to exclude from ScimResource, may be used to optimize queries.
* @return The newly updated ScimResource.
- * @throws ResourceException When the ScimResource cannot be
- * updated.
+ * @throws ResourceException When the ScimResource cannot be updated.
*/
- T update(UpdateRequest<T> updateRequest) throws ResourceException;
-
+ T update(String id, String version, T resource, Set<AttributeReference> includedAttributes, Set<AttributeReference> excludedAttributes) throws ResourceException;
+
+ /**
+ * Allows the SCIM server's REST implementation to update and existing
+ * resource via a PATCH to a valid end-point.
+ * <br>
+ * <b>SCIM Implementation NOTE:</b> SCIM supports <a href="https://datatracker.ietf.org/doc/html/rfc7644#section-3.14">versioning of resources via HTTP ETags</a>, if the (optional) {@code version} parameter is present, (and supported by the server),
+ * it can be used as a mechanism for caching and to ensure clients do not inadvertently overwrite other changes.
+ *
+ * @param id the identifier of the ScimResource to update and persist.
+ * @param version an optional version (usually used as an ETag) that can be used to optimize update requests, may be compared against, the current {@code ScimResource.meta.version}.
+ * @param patchOperations a list of patch operations to apply to an existing resource.
+ * @param includedAttributes optional set of attributes to include from ScimResource, may be used to optimize queries.
+ * @param excludedAttributes optional set of attributes to exclude from ScimResource, may be used to optimize queries.
+ * @return The newly updated ScimResource.
+ * @throws ResourceException When the ScimResource cannot be updated.
+ */
+ T patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributes, Set<AttributeReference> excludedAttributes) throws ResourceException;
+
/**
* Retrieves the ScimResource associated with the provided identifier.
* @param id The identifier of the target ScimResource.
diff --git a/scim-core/src/main/java/org/apache/directory/scim/core/repository/UpdateRequest.java b/scim-core/src/main/java/org/apache/directory/scim/core/repository/UpdateRequest.java
deleted file mode 100644
index 52a2e245..00000000
--- a/scim-core/src/main/java/org/apache/directory/scim/core/repository/UpdateRequest.java
+++ /dev/null
@@ -1,585 +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.core.repository;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import com.fasterxml.jackson.databind.node.BooleanNode;
-import com.fasterxml.jackson.databind.node.DoubleNode;
-import com.fasterxml.jackson.databind.node.FloatNode;
-import com.fasterxml.jackson.databind.node.IntNode;
-import com.fasterxml.jackson.databind.node.NullNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import com.fasterxml.jackson.databind.node.POJONode;
-import com.fasterxml.jackson.databind.node.TextNode;
-import com.flipkart.zjsonpatch.DiffFlags;
-import com.flipkart.zjsonpatch.JsonDiff;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.ToString;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.directory.scim.core.json.ObjectMapperFactory;
-import org.apache.directory.scim.core.schema.SchemaRegistry;
-import org.apache.directory.scim.spec.filter.*;
-import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
-import org.apache.directory.scim.spec.patch.PatchOperation;
-import org.apache.directory.scim.spec.patch.PatchOperation.Type;
-import org.apache.directory.scim.spec.patch.PatchOperationPath;
-import org.apache.directory.scim.spec.resources.ScimExtension;
-import org.apache.directory.scim.spec.resources.ScimResource;
-import org.apache.directory.scim.spec.resources.TypedAttribute;
-import org.apache.directory.scim.spec.schema.AttributeContainer;
-import org.apache.directory.scim.spec.schema.Schema;
-import org.apache.directory.scim.spec.schema.Schema.Attribute;
-
-import java.util.*;
-import java.util.stream.Collectors;
-
-@Slf4j
-@EqualsAndHashCode
-@ToString
-public class UpdateRequest<T extends ScimResource> {
-
- private static final String OPERATION = "op";
- private static final String PATH = "path";
- private static final String VALUE = "value";
-
- // Create a Jackson ObjectMapper that reads JaxB annotations
- private final ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
-
- @Getter
- private String id;
- private T resource;
- @Getter
- private T original;
- private List<PatchOperation> patchOperations;
- private boolean initialized = false;
-
- private Schema schema;
-
- private PatchHandler patchHandler;
-
- private SchemaRegistry schemaRegistry;
-
- private Map<Attribute, Integer> addRemoveOffsetMap = new HashMap<>();
-
- public UpdateRequest(SchemaRegistry schemaRegistry) {
- this.schemaRegistry = schemaRegistry;
- }
-
- public UpdateRequest(String id, T original, T resource, SchemaRegistry schemaRegistry) {
- this(schemaRegistry);
- this.id = id;
- this.original = original;
- this.resource = resource;
- this.patchHandler = new PatchHandlerImpl(schemaRegistry);
- this.schema = schemaRegistry.getSchema(original.getBaseUrn());
- initialized = true;
- }
-
- public UpdateRequest(String id, T original, List<PatchOperation> patchOperations, SchemaRegistry schemaRegistry) {
- this(schemaRegistry);
- this.id = id;
- this.original = original;
- this.patchOperations = patchOperations;
- this.patchHandler = new PatchHandlerImpl(schemaRegistry);
- this.schema = schemaRegistry.getSchema(original.getBaseUrn());
-
- initialized = true;
- }
-
- public T getResource() {
- if (!initialized) {
- throw new IllegalStateException("UpdateRequest was not initialized");
- }
-
- if (resource == null) {
- this.resource = this.patchHandler.apply(this.original, this.patchOperations);
- }
-
- return this.resource;
- }
-
- public List<PatchOperation> getPatchOperations() {
- if (!initialized) {
- throw new IllegalStateException("UpdateRequest was not initialized");
- }
-
- if (patchOperations == null) {
- try {
- patchOperations = createPatchOperations();
- } catch (IllegalArgumentException | IllegalAccessException | JsonProcessingException e) {
- throw new IllegalStateException("Error creating the patch list", e);
- }
- }
-
- return patchOperations;
- }
-
- private void sortMultiValuedCollections(Object obj1, Object obj2, AttributeContainer ac) throws IllegalArgumentException {
- for (Attribute attribute : ac.getAttributes()) {
- Schema.AttributeAccessor accessor = attribute.getAccessor();
- if (attribute.isMultiValued()) {
- @SuppressWarnings("unchecked")
- List<Object> collection1 = obj1 != null ? (List<Object>) accessor.get(obj1) : null;
- @SuppressWarnings("unchecked")
- List<Object> collection2 = obj2 != null ? (List<Object>) accessor.get(obj2) : null;
-
- Set<Object> priorities = findCommonElements(collection1, collection2);
- PrioritySortingComparator prioritySortingComparator = new PrioritySortingComparator(priorities);
- if (collection1 != null) {
- Collections.sort(collection1, prioritySortingComparator);
- }
-
- if (collection2 != null) {
- Collections.sort(collection2, prioritySortingComparator);
- }
- } else if (attribute.getType() == Attribute.Type.COMPLEX) {
- Object nextObj1 = obj1 != null ? accessor.get(obj1) : null;
- Object nextObj2 = obj2 != null ? accessor.get(obj2) : null;
- sortMultiValuedCollections(nextObj1, nextObj2, attribute);
- }
- }
- }
-
- private Set<Object> findCommonElements(List<Object> list1, List<Object> list2) {
- if (list1 == null || list2 == null) {
- return Collections.emptySet();
- }
-
- Set<Object> set1 = new HashSet<>(list1);
- Set<Object> set2 = new HashSet<>(list2);
-
- set1 = set1.stream().map(PrioritySortingComparator::getComparableValue).collect(Collectors.toSet());
- set2 = set2.stream().map(PrioritySortingComparator::getComparableValue).collect(Collectors.toSet());
-
- set1.retainAll(set2);
- return set1;
- }
-
- /**
- * There is a know issue with the diffing tool that the tool will attempt to move empty arrays. By
- * nulling out the empty arrays during comparison, this will prevent that error from occurring. Because
- * deleting requires the parent node
- * @param node Parent node.
- */
- private static void nullEmptyLists(JsonNode node) {
- List<String> objectsToDelete = new ArrayList<>();
-
- if (node != null) {
- Iterator<Map.Entry<String, JsonNode>> children = node.fields();
- while(children.hasNext()) {
- Map.Entry<String, JsonNode> child = children.next();
- String name = child.getKey();
- JsonNode childNode = child.getValue();
-
- //Attempt to delete children before analyzing
- if (childNode.isContainerNode()) {
- nullEmptyLists(childNode);
- }
-
- if (childNode instanceof ArrayNode) {
- ArrayNode ar = (ArrayNode)childNode;
- if (ar.size() == 0) {
- objectsToDelete.add(name);
- }
- }
- }
-
- if (!objectsToDelete.isEmpty() && node instanceof ObjectNode) {
- ObjectNode on = (ObjectNode)node;
- for(String name : objectsToDelete) {
- on.remove(name);
- }
- }
- }
- }
-
- private List<PatchOperation> createPatchOperations() throws IllegalArgumentException, IllegalAccessException, JsonProcessingException {
-
- sortMultiValuedCollections(this.original, this.resource, schema);
- Map<String, ScimExtension> originalExtensions = this.original.getExtensions();
- Map<String, ScimExtension> resourceExtensions = this.resource.getExtensions();
- Set<String> keys = new HashSet<>();
- keys.addAll(originalExtensions.keySet());
- keys.addAll(resourceExtensions.keySet());
-
- for(String key: keys) {
- Schema extSchema = schemaRegistry.getSchema(key);
- ScimExtension originalExtension = originalExtensions.get(key);
- ScimExtension resourceExtension = resourceExtensions.get(key);
- sortMultiValuedCollections(originalExtension, resourceExtension, extSchema);
- }
-
- JsonNode node1 = objectMapper.valueToTree(original);
- nullEmptyLists(node1);
- JsonNode node2 = objectMapper.valueToTree(resource);
- nullEmptyLists(node2);
- JsonNode differences = JsonDiff.asJson(node1, node2, DiffFlags.dontNormalizeOpIntoMoveAndCopy());
-
-
- /*
- Commenting out debug statement to prevent PII from appearing in log
- ObjectWriter writer = objMapper.writerWithDefaultPrettyPrinter();
- try {
- log.debug("Original: "+writer.writeValueAsString(node1));
- log.debug("Resource: "+writer.writeValueAsString(node2));
- } catch (IOException e) {
-
- }*/
-
- /*try {
- log.debug("Differences: " + objMapper.writerWithDefaultPrettyPrinter().writeValueAsString(differences));
- } catch (JsonProcessingException e) {
- log.debug("Unable to debug differences: ", e);
- }*/
-
- List<PatchOperation> patchOps = convertToPatchOperations(differences);
-
- /*try {
- log.debug("Patch Ops: " + objMapper.writerWithDefaultPrettyPrinter().writeValueAsString(patchOps));
- } catch (JsonProcessingException e) {
- log.debug("Unable to debug patch ops: ", e);
- }*/
-
- return patchOps;
- }
-
- List<PatchOperation> convertToPatchOperations(JsonNode node) throws IllegalArgumentException, IllegalAccessException, JsonProcessingException {
- List<PatchOperation> operations = new ArrayList<>();
- if (node == null) {
- return Collections.emptyList();
- }
-
- if (!(node instanceof ArrayNode)) {
- throw new RuntimeException("Expecting an instance of a ArrayNode, but got: " + node.getClass());
- }
-
- ArrayNode root = (ArrayNode) node;
- for (int i = 0; i < root.size(); i++) {
- ObjectNode patchNode = (ObjectNode) root.get(i);
- JsonNode operationNode = patchNode.get(OPERATION);
- JsonNode pathNode = patchNode.get(PATH);
- JsonNode valueNode = patchNode.get(VALUE);
-
- List<PatchOperation> nodeOperations = convertNodeToPatchOperations(operationNode.asText(), pathNode.asText(), valueNode);
- if (!nodeOperations.isEmpty()) {
- operations.addAll(nodeOperations);
- }
- }
-
- return operations;
- }
-
- private List<PatchOperation> convertNodeToPatchOperations(String operationNode, String diffPath, JsonNode valueNode) throws IllegalArgumentException, IllegalAccessException, JsonProcessingException {
- log.debug("convertNodeToPatchOperations: {} , {}", operationNode, diffPath);
- List<PatchOperation> operations = new ArrayList<>();
- PatchOperation.Type patchOpType = PatchOperation.Type.valueOf(operationNode.toUpperCase(Locale.ROOT));
-
- if (diffPath != null && diffPath.length() >= 1) {
- ParseData parseData = new ParseData(diffPath);
-
- if (parseData.pathParts.isEmpty()) {
- operations.add(handleExtensions(valueNode, patchOpType, parseData));
- } else {
- operations.addAll(handleAttributes(valueNode, patchOpType, parseData));
- }
- }
-
- return operations;
- }
-
- private PatchOperation handleExtensions(JsonNode valueNode, Type patchOpType, ParseData parseData) throws JsonProcessingException {
- PatchOperation operation = new PatchOperation();
- operation.setOperation(patchOpType);
-
- AttributeReference attributeReference = new AttributeReference(parseData.pathUri, null);
- PatchOperationPath patchOperationPath = new PatchOperationPath(new ValuePathExpression(attributeReference));
-
- operation.setPath(patchOperationPath);
- operation.setValue(determineValue(patchOpType, valueNode, parseData));
-
- return operation;
- }
-
- @SuppressWarnings("unchecked")
- private List<PatchOperation> handleAttributes(JsonNode valueNode, PatchOperation.Type patchOpType, ParseData parseData) throws IllegalAccessException, JsonProcessingException {
- log.trace("in handleAttributes");
- List<PatchOperation> operations = new ArrayList<>();
-
- List<String> attributeReferenceList = new ArrayList<>();
- FilterExpression valueFilterExpression = null;
- List<String> subAttributes = new ArrayList<>();
-
- boolean processingMultiValued = false;
- boolean processedMultiValued = false;
- boolean done = false;
-
- int i = 0;
- for (String pathPart : parseData.pathParts) {
- log.trace("path part: {}", pathPart);
- if (done) {
- throw new RuntimeException("Path should be done... Attribute not supported by the schema: " + pathPart);
- } else if (processingMultiValued) {
- parseData.traverseObjectsInArray(pathPart, patchOpType);
-
- if (!parseData.isLastIndex(i) || patchOpType != PatchOperation.Type.ADD) {
- if (parseData.originalObject instanceof TypedAttribute) {
- TypedAttribute typedAttribute = (TypedAttribute) parseData.originalObject;
- String type = typedAttribute.getType();
- valueFilterExpression = new AttributeComparisonExpression(new AttributeReference("type"), CompareOperator.EQ, type);
- } else if (parseData.originalObject instanceof String || parseData.originalObject instanceof Number) {
- String toString = parseData.originalObject.toString();
- valueFilterExpression = new AttributeComparisonExpression(new AttributeReference("value"), CompareOperator.EQ, toString);
- } else if(parseData.originalObject instanceof Enum) {
- Enum<?> tempEnum = (Enum<?>)parseData.originalObject;
- valueFilterExpression = new AttributeComparisonExpression(new AttributeReference("value"), CompareOperator.EQ, tempEnum.name());
- } else {
- if (parseData.originalObject != null) {
- log.debug("Attribute: {} doesn't implement TypedAttribute, can't create ValueFilterExpression", parseData.originalObject.getClass());
- } else {
- log.debug("Attribute: null doesn't implement TypedAttribute, can't create ValueFilterExpression");
- }
- valueFilterExpression = new AttributeComparisonExpression(new AttributeReference("value"), CompareOperator.EQ, "?");
- }
- processingMultiValued = false;
- processedMultiValued = true;
- }
- } else {
- Attribute attribute = parseData.ac.getAttribute(pathPart);
-
- if (attribute != null) {
- if (processedMultiValued) {
- subAttributes.add(pathPart);
- } else {
- log.debug("Adding {} to attributeReferenceList", pathPart);
- attributeReferenceList.add(pathPart);
- }
-
- parseData.traverseObjects(pathPart, attribute);
-
- if (patchOpType == Type.REPLACE &&
- (parseData.resourceObject != null && parseData.resourceObject instanceof Collection && !((Collection<?>)parseData.resourceObject).isEmpty()) &&
- (parseData.originalObject == null ||
- (parseData.originalObject instanceof Collection && ((Collection<?>)parseData.originalObject).isEmpty()))) {
- patchOpType = Type.ADD;
- }
-
- if (attribute.isMultiValued()) {
- processingMultiValued = true;
- } else if (attribute.getType() != Attribute.Type.COMPLEX) {
- done = true;
- }
- }
- }
- ++i;
- }
-
- if (patchOpType == Type.REPLACE && (parseData.resourceObject == null ||
- (parseData.resourceObject instanceof Collection && ((Collection<?>)parseData.resourceObject).isEmpty()))) {
- patchOpType = Type.REMOVE;
- valueNode = null;
- }
-
- if (patchOpType == Type.REPLACE && parseData.originalObject == null) {
- patchOpType = Type.ADD;
- }
-
- if (!attributeReferenceList.isEmpty()) {
- Object value = determineValue(patchOpType, valueNode, parseData);
-
- if (value != null && value instanceof ArrayList) {
- List<Object> objList = (List<Object>)value;
-
- if (!objList.isEmpty()) {
- Object firstElement = objList.get(0);
- if (firstElement instanceof ArrayList) {
- objList = (List<Object>) firstElement;
- }
-
- for (Object obj : objList) {
- PatchOperation operation = buildPatchOperation(patchOpType, parseData, attributeReferenceList, valueFilterExpression, subAttributes, obj);
- if (operation != null) {
- operations.add(operation);
- }
- }
- }
- } else {
- PatchOperation operation = buildPatchOperation(patchOpType, parseData, attributeReferenceList, valueFilterExpression, subAttributes, value);
- if (operation != null) {
- operations.add(operation);
- }
- }
- }
-
- return operations;
- }
-
- private PatchOperation buildPatchOperation(PatchOperation.Type patchOpType, ParseData parseData, List<String> attributeReferenceList,
- FilterExpression valueFilterExpression, List<String> subAttributes, Object value) {
- PatchOperation operation = new PatchOperation();
- operation.setOperation(patchOpType);
- String attribute = attributeReferenceList.get(0);
- String subAttribute = attributeReferenceList.size() > 1 ? attributeReferenceList.get(1) : null;
-
- if (subAttribute == null && !subAttributes.isEmpty()) {
- subAttribute = subAttributes.get(0);
- }
- AttributeReference attributeReference = new AttributeReference(parseData.pathUri, attribute, subAttribute);
- PatchOperationPath patchOperationPath = new PatchOperationPath(new ValuePathExpression(attributeReference, valueFilterExpression));
-
- operation.setPath(patchOperationPath);
- operation.setValue(value);
-
- return operation;
- }
-
- private Object determineValue(PatchOperation.Type patchOpType, JsonNode valueNode, ParseData parseData) throws JsonProcessingException {
- if (patchOpType == PatchOperation.Type.REMOVE) {
- return null;
- }
-
- if (valueNode != null) {
- if (valueNode instanceof TextNode) {
- return valueNode.asText();
- } else if (valueNode instanceof BooleanNode) {
- return valueNode.asBoolean();
- } else if (valueNode instanceof DoubleNode || valueNode instanceof FloatNode) {
- return valueNode.asDouble();
- } else if (valueNode instanceof IntNode) {
- return valueNode.asInt();
- } else if (valueNode instanceof NullNode) {
- return null;
- } else if (valueNode instanceof ObjectNode) {
- return parseData.resourceObject;
- } else if (valueNode instanceof POJONode) {
- POJONode pojoNode = (POJONode) valueNode;
- return pojoNode.getPojo();
- } else if (valueNode instanceof ArrayNode) {
- ArrayNode arrayNode = (ArrayNode) valueNode;
- List<Object> objectList = new ArrayList<>();
- for(int i = 0; i < arrayNode.size(); i++) {
- Object subObject = determineValue(patchOpType, arrayNode.get(i), parseData);
- if (subObject != null) {
- objectList.add(subObject);
- }
- }
- return objectList;
- }
- }
- return null;
- }
-
- private class ParseData {
-
- List<String> pathParts;
- Object originalObject;
- Object resourceObject;
- AttributeContainer ac;
- String pathUri;
-
- public ParseData(String diffPath) {
- String path = diffPath.substring(1);
- pathParts = new ArrayList<>(Arrays.asList(path.split("/")));
-
- // Extract namespace
- pathUri = null;
-
- String firstPathPart = pathParts.get(0);
- if (firstPathPart.contains(":")) {
- pathUri = firstPathPart;
- pathParts.remove(0);
- }
-
- if (pathUri != null) {
- ac = schemaRegistry.getSchema(pathUri);
- originalObject = original.getExtension(pathUri);
- resourceObject = resource.getExtension(pathUri);
- } else {
- ac = schema;
- originalObject = original;
- resourceObject = resource;
- }
- }
-
- public void traverseObjects(String pathPart, Attribute attribute) throws IllegalArgumentException, IllegalAccessException {
- originalObject = lookupAttribute(originalObject, ac, pathPart);
- resourceObject = lookupAttribute(resourceObject, ac, pathPart);
- ac = attribute;
- }
-
- public void traverseObjectsInArray(String pathPart, Type patchOpType) {
- int index = Integer.parseInt(pathPart);
-
- Attribute attr = (Attribute) ac;
-
- Integer addRemoveOffset = addRemoveOffsetMap.getOrDefault(attr, 0);
- switch (patchOpType) {
- case ADD:
- addRemoveOffsetMap.put(attr, addRemoveOffset - 1);
- break;
- case REMOVE:
- addRemoveOffsetMap.put(attr, addRemoveOffset + 1);
- break;
- case REPLACE:
- default:
- // Do Nothing
- break;
- }
-
- int newindex = index + addRemoveOffset;
- if (newindex < 0) {
- log.error("Attempting to retrieve a negative index:{} on pathPath: {}", newindex, pathPart);
- }
-
- originalObject = lookupIndexInArray(originalObject, newindex);
- resourceObject = lookupIndexInArray(resourceObject, index);
- }
-
- public boolean isLastIndex(int index) {
- int numPathParts = pathParts.size();
- return index == (numPathParts - 1);
- }
-
- @SuppressWarnings("rawtypes")
- private Object lookupIndexInArray(Object object, int index) {
- if (!(object instanceof List)) {
- throw new RuntimeException("Unsupported collection type: " + object.getClass());
- }
- List list = (List) object;
- if (index >= list.size()) {
- return null;
- }
-
- return list.get(index);
- }
-
- private Object lookupAttribute(Object object, AttributeContainer ac, String attributeName) throws IllegalArgumentException, IllegalAccessException {
- if (object == null) {
- return null;
- }
-
- Attribute attribute = ac.getAttribute(attributeName);
- return attribute.getAccessor().get(object);
- }
- }
-}
diff --git a/scim-core/src/test/java/org/apache/directory/scim/core/repository/UpdateRequestTest.java b/scim-core/src/test/java/org/apache/directory/scim/core/repository/PatchGeneratorTest.java
similarity index 84%
rename from scim-core/src/test/java/org/apache/directory/scim/core/repository/UpdateRequestTest.java
rename to scim-core/src/test/java/org/apache/directory/scim/core/repository/PatchGeneratorTest.java
index 3dbdfedb..babd7a38 100644
--- a/scim-core/src/test/java/org/apache/directory/scim/core/repository/UpdateRequestTest.java
+++ b/scim-core/src/test/java/org/apache/directory/scim/core/repository/PatchGeneratorTest.java
@@ -19,7 +19,7 @@
package org.apache.directory.scim.core.repository;
-import static org.apache.directory.scim.core.repository.UpdateRequestTest.PatchOperationCondition.op;
+import static org.apache.directory.scim.core.repository.PatchGeneratorTest.PatchOperationCondition.op;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
@@ -64,7 +64,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@Slf4j
-public class UpdateRequestTest {
+public class PatchGeneratorTest {
private static final String FIRST = "first";
private static final String SECOND = "second";
@@ -85,31 +85,13 @@ public class UpdateRequestTest {
when(schemaRegistry.getSchema(ExampleObjectExtension.URN)).thenReturn(Schemas.schemaForExtension(ExampleObjectExtension.class));
}
- @Test
- public void testResourcePassthrough() throws Exception {
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", createUser(), createUser(), schemaRegistry);
- ScimUser result = updateRequest.getResource();
- log.debug("testResourcePassthrough: {}", result);
- assertThat(result)
- .isNotNull();
- }
-
- @Test
- public void testPatchPassthrough() throws Exception {
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", createUser(), createUser1PatchOps(), schemaRegistry);
- List<PatchOperation> result = updateRequest.getPatchOperations();
- log.debug("testPatchPassthrough: {}", result);
- assertThat(result)
- .isNotNull();
- }
@Test
public void testAddSingleAttribute() throws Exception {
ScimUser user1 = createUser();
ScimUser user2 = createUser();
user2.setNickName("Jon");
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> result = updateRequest.getPatchOperations();
+ List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
PatchOperation actual = assertSingleResult(result);
@@ -123,8 +105,7 @@ public class UpdateRequestTest {
ScimUser user2 = createUser();
user2.addExtension(ext);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> result = updateRequest.getPatchOperations();
+ List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
PatchOperation actual = assertSingleResult(result);
@@ -138,8 +119,7 @@ public class UpdateRequestTest {
user2.getName()
.setHonorificPrefix("Dr.");
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> result = updateRequest.getPatchOperations();
+ List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
PatchOperation actual = assertSingleResult(result);
@@ -155,8 +135,7 @@ public class UpdateRequestTest {
mobilePhone.setPrimary(false);
user2.getPhoneNumbers().add(mobilePhone);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> result = updateRequest.getPatchOperations();
+ List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
PatchOperation actual = assertSingleResult(result);
@@ -181,8 +160,8 @@ public class UpdateRequestTest {
mobilePhone.setPrimary(true);
user2.getPhoneNumbers().add(mobilePhone);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> operations = updateRequest.getPatchOperations();
+ List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
+
assertNotNull(operations);
assertThat(operations).hasSize(1);
PatchOperation operation = operations.get(0);
@@ -210,8 +189,7 @@ public class UpdateRequestTest {
user2.getPhoneNumbers().add(homePhone);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> operations = updateRequest.getPatchOperations();
+ List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
assertNotNull(operations);
assertEquals(2, operations.size());
@@ -232,8 +210,7 @@ public class UpdateRequestTest {
ScimUser user2 = createUser();
user2.setActive(false);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> result = updateRequest.getPatchOperations();
+ List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
PatchOperation actual = assertSingleResult(result);
@@ -246,8 +223,7 @@ public class UpdateRequestTest {
ScimUser user2 = createUser();
user2.getExtension(EnterpriseExtension.class).setDepartment("Dept XYZ.");
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> result = updateRequest.getPatchOperations();
+ List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
PatchOperation actual = assertSingleResult(result);
@@ -261,8 +237,7 @@ public class UpdateRequestTest {
user2.getName()
.setFamilyName("Nobody");
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> result = updateRequest.getPatchOperations();
+ List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
PatchOperation actual = assertSingleResult(result);
@@ -279,8 +254,7 @@ public class UpdateRequestTest {
.equals("work"))
.forEach(e -> e.setValue("nobody@example.com"));
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> result = updateRequest.getPatchOperations();
+ List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
PatchOperation actual = assertSingleResult(result);
@@ -293,8 +267,7 @@ public class UpdateRequestTest {
ScimUser user2 = createUser();
user2.setUserName(null);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> result = updateRequest.getPatchOperations();
+ List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
PatchOperation actual = assertSingleResult(result);
@@ -307,8 +280,7 @@ public class UpdateRequestTest {
ScimUser user2 = createUser();
user2.removeExtension(EnterpriseExtension.class);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> result = updateRequest.getPatchOperations();
+ List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
PatchOperation actual = assertSingleResult(result);
@@ -322,9 +294,7 @@ public class UpdateRequestTest {
user2.getName()
.setMiddleName(null);
-
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> result = updateRequest.getPatchOperations();
+ List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
PatchOperation actual = assertSingleResult(result);
@@ -337,8 +307,7 @@ public class UpdateRequestTest {
ScimUser user2 = createUser();
user2.setName(null);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> result = updateRequest.getPatchOperations();
+ List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
PatchOperation actual = assertSingleResult(result);
@@ -356,8 +325,7 @@ public class UpdateRequestTest {
.collect(Collectors.toList());
user2.setEmails(newEmails);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> result = updateRequest.getPatchOperations();
+ List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
PatchOperation actual = assertSingleResult(result);
@@ -378,8 +346,7 @@ public class UpdateRequestTest {
user1.getAddresses().add(localAddress);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> result = updateRequest.getPatchOperations();
+ List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
PatchOperation actual = assertSingleResult(result);
@@ -401,8 +368,7 @@ public class UpdateRequestTest {
user2.getAddresses().add(localAddress);
user1.getAddresses().get(0).setPostalCode("01234");
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> result = updateRequest.getPatchOperations();
+ List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
assertEquals(2, result.size());
@@ -422,8 +388,7 @@ public class UpdateRequestTest {
ext2.setList(new ArrayList<>());
user2.addExtension(ext2);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> operations = updateRequest.getPatchOperations();
+ List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
assertTrue(operations.isEmpty(), "Empty Arrays caused a diff");
}
@@ -431,18 +396,19 @@ public class UpdateRequestTest {
public void verifyEmptyArraysAreNulled() throws Exception {
ScimUser user1 = createUser();
ScimUser user2 = createUser();
-
+
+ PatchGenerator patchGenerator = new PatchGenerator(schemaRegistry);
+
//Set empty list on root object and verify no differences
user1.setPhotos(new ArrayList<>());
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> operations = updateRequest.getPatchOperations();
+ List<PatchOperation> operations = patchGenerator.diff(user1, user2);
assertTrue(operations.isEmpty(), "Empty Arrays are not being nulled out");
//Reset user 1 and empty list on Extension and verify no differences
user1 = createUser();
ExampleObjectExtension ext = new ExampleObjectExtension();
ext.setList(new ArrayList<>());
- operations = updateRequest.getPatchOperations();
+ operations = patchGenerator.diff(user1, user2);
assertTrue(operations.isEmpty(), "Empty Arrays are not being nulled out");
//Reset extension and set empty list on element of extension then verify no differences
@@ -450,7 +416,7 @@ public class UpdateRequestTest {
subobject.setList1(new ArrayList<>());
ext = new ExampleObjectExtension();
ext.setSubobject(subobject);
- operations = updateRequest.getPatchOperations();
+ operations = patchGenerator.diff(user1, user2);
assertTrue(operations.isEmpty(), "Empty Arrays are not being nulled out");
}
@@ -476,8 +442,7 @@ public class UpdateRequestTest {
ext2.setList(Stream.of(FIRST,SECOND).collect(Collectors.toList()));
user2.addExtension(ext2);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> operations = updateRequest.getPatchOperations();
+ List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
assertThat(operations)
.hasSize(3)
@@ -507,8 +472,7 @@ public class UpdateRequestTest {
ext2.setList(null);
user2.addExtension(ext2);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> operations = updateRequest.getPatchOperations();
+ List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
assertNotNull(operations);
assertEquals(2, operations.size());
PatchOperation operation = operations.get(0);
@@ -529,8 +493,7 @@ public class UpdateRequestTest {
ext2.setList(Stream.of(FIRST,SECOND,FOURTH).collect(Collectors.toList()));
user2.addExtension(ext2);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> operations = updateRequest.getPatchOperations();
+ List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
assertThat(operations)
.hasSize(1);
@@ -551,8 +514,7 @@ public class UpdateRequestTest {
user2.getName().setFormatted(nickname);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> operations = updateRequest.getPatchOperations();
+ List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
assertThat(operations).hasSize(2);
assertThat(operations).filteredOn(op(Type.ADD, "name.formatted", nickname)).hasSize(1);
@@ -571,8 +533,7 @@ public class UpdateRequestTest {
user1.getName().setFormatted("");
user2.getName().setFormatted(nickname);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> operations = updateRequest.getPatchOperations();
+ List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
assertThat(operations).hasSize(2);
assertThat(operations).filteredOn(op(Type.REPLACE, "name.formatted", nickname)).hasSize(1);
@@ -591,8 +552,7 @@ public class UpdateRequestTest {
user1.getName().setFormatted("");
user2.getName().setFormatted(nickname);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> operations = updateRequest.getPatchOperations();
+ List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
assertThat(operations).hasSize(2);
assertThat(operations).filteredOn(op(Type.REPLACE, "name.formatted", nickname)).hasSize(1);
@@ -612,8 +572,7 @@ public class UpdateRequestTest {
user1.getName().setFormatted(null);
user2.getName().setFormatted(nickname);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> operations = updateRequest.getPatchOperations();
+ List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
assertThat(operations).hasSize(2);
assertThat(operations).filteredOn(op(Type.ADD, "name.formatted", nickname)).hasSize(1);
@@ -632,8 +591,7 @@ public class UpdateRequestTest {
user1.getName().setFormatted(nickname);
user2.getName().setFormatted(null);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> operations = updateRequest.getPatchOperations();
+ List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
assertThat(operations).hasSize(2);
assertThat(operations).filteredOn(op(Type.REMOVE, "name.formatted")).hasSize(1);
@@ -654,8 +612,7 @@ public class UpdateRequestTest {
ext2.setList(list2);
user2.addExtension(ext2);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> operations = updateRequest.getPatchOperations();
+ List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
assertEquals(ops.size(), operations.size());
for(int i = 0; i < operations.size(); i++) {
PatchOperation actualOp = operations.get(i);
@@ -722,8 +679,7 @@ public class UpdateRequestTest {
ext2.setList(Stream.of("A","Z").collect(Collectors.toList()));
user2.addExtension(ext2);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> operations = updateRequest.getPatchOperations();
+ List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
System.out.println("Number of operations: "+operations.size());
operations.stream().forEach(op -> System.out.println(op));
@@ -749,8 +705,7 @@ public class UpdateRequestTest {
user1.getName().setFormatted(nickname);
user2.getName().setFormatted("");
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> operations = updateRequest.getPatchOperations();
+ List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
assertThat(operations).hasSize(2);
assertThat(operations).filteredOn(op(Type.REPLACE, "name.formatted", "")).hasSize(1);
@@ -777,8 +732,7 @@ public class UpdateRequestTest {
assertNotNull(workNumber);
workNumber.setType(null);
- UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
- List<PatchOperation> operations = updateRequest.getPatchOperations();
+ List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
assertNotNull(operations);
System.out.println("Number of operations: "+operations.size());
diff --git a/scim-core/src/test/java/org/apache/directory/scim/core/repository/PatchHandlerTest.java b/scim-core/src/test/java/org/apache/directory/scim/core/repository/PatchHandlerTest.java
index 2cd31c2f..9dcafeea 100644
--- a/scim-core/src/test/java/org/apache/directory/scim/core/repository/PatchHandlerTest.java
+++ b/scim-core/src/test/java/org/apache/directory/scim/core/repository/PatchHandlerTest.java
@@ -44,12 +44,12 @@ import java.util.Optional;
public class PatchHandlerTest {
- PatchHandlerImpl patchHandler;
+ DefaultPatchHandler patchHandler;
public PatchHandlerTest() {
SchemaRegistry schemaRegistry = new SchemaRegistry();
schemaRegistry.addSchema(ScimUser.class, List.of(EnterpriseExtension.class));
- this.patchHandler = new PatchHandlerImpl(schemaRegistry);
+ this.patchHandler = new DefaultPatchHandler(schemaRegistry);
}
@Test
diff --git a/scim-server-examples/scim-server-jersey/src/main/java/org/apache/directory/scim/example/jersey/service/InMemoryGroupService.java b/scim-server-examples/scim-server-jersey/src/main/java/org/apache/directory/scim/example/jersey/service/InMemoryGroupService.java
index 575e2add..42b09e31 100644
--- a/scim-server-examples/scim-server-jersey/src/main/java/org/apache/directory/scim/example/jersey/service/InMemoryGroupService.java
+++ b/scim-server-examples/scim-server-jersey/src/main/java/org/apache/directory/scim/example/jersey/service/InMemoryGroupService.java
@@ -23,15 +23,17 @@ import jakarta.annotation.PostConstruct;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
+import org.apache.directory.scim.core.repository.PatchHandler;
import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
+import org.apache.directory.scim.spec.exception.ResourceException;
import org.apache.directory.scim.spec.filter.FilterExpressions;
import org.apache.directory.scim.spec.filter.FilterResponse;
import org.apache.directory.scim.spec.filter.Filter;
import org.apache.directory.scim.spec.filter.PageRequest;
import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
import org.apache.directory.scim.spec.resources.ScimExtension;
import org.apache.directory.scim.spec.resources.ScimGroup;
@@ -39,6 +41,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -54,9 +57,12 @@ public class InMemoryGroupService implements Repository<ScimGroup> {
private SchemaRegistry schemaRegistry;
+ private PatchHandler patchHandler;
+
@Inject
- public InMemoryGroupService(SchemaRegistry schemaRegistry) {
+ public InMemoryGroupService(SchemaRegistry schemaRegistry, PatchHandler patchHandler) {
this.schemaRegistry = schemaRegistry;
+ this.patchHandler = patchHandler;
}
protected InMemoryGroupService() {}
@@ -98,9 +104,14 @@ public class InMemoryGroupService implements Repository<ScimGroup> {
}
@Override
- public ScimGroup update(UpdateRequest<ScimGroup> updateRequest) throws UnableToUpdateResourceException {
- String id = updateRequest.getId();
- ScimGroup resource = updateRequest.getResource();
+ public ScimGroup update(String id, String version, ScimGroup resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ groups.put(id, resource);
+ return resource;
+ }
+
+ @Override
+ public ScimGroup patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ ScimGroup resource = patchHandler.apply(get(id), patchOperations);
groups.put(id, resource);
return resource;
}
diff --git a/scim-server-examples/scim-server-jersey/src/main/java/org/apache/directory/scim/example/jersey/service/InMemoryUserService.java b/scim-server-examples/scim-server-jersey/src/main/java/org/apache/directory/scim/example/jersey/service/InMemoryUserService.java
index 24393fcc..2f000137 100644
--- a/scim-server-examples/scim-server-jersey/src/main/java/org/apache/directory/scim/example/jersey/service/InMemoryUserService.java
+++ b/scim-server-examples/scim-server-jersey/src/main/java/org/apache/directory/scim/example/jersey/service/InMemoryUserService.java
@@ -22,6 +22,7 @@ package org.apache.directory.scim.example.jersey.service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -31,17 +32,19 @@ import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.ws.rs.core.Response;
+import org.apache.directory.scim.core.repository.PatchHandler;
import org.apache.directory.scim.example.jersey.extensions.LuckyNumberExtension;
import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
+import org.apache.directory.scim.spec.exception.ResourceException;
import org.apache.directory.scim.spec.extension.EnterpriseExtension;
import org.apache.directory.scim.spec.filter.FilterExpressions;
import org.apache.directory.scim.spec.filter.FilterResponse;
import org.apache.directory.scim.spec.filter.Filter;
import org.apache.directory.scim.spec.filter.PageRequest;
import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
import org.apache.directory.scim.spec.resources.*;
import org.apache.directory.scim.core.schema.SchemaRegistry;
@@ -66,9 +69,12 @@ public class InMemoryUserService implements Repository<ScimUser> {
private SchemaRegistry schemaRegistry;
+ private PatchHandler patchHandler;
+
@Inject
- public InMemoryUserService(SchemaRegistry schemaRegistry) {
+ public InMemoryUserService(SchemaRegistry schemaRegistry, PatchHandler patchHandler) {
this.schemaRegistry = schemaRegistry;
+ this.patchHandler = patchHandler;
}
protected InMemoryUserService() {}
@@ -130,17 +136,20 @@ public class InMemoryUserService implements Repository<ScimUser> {
return resource;
}
- /**
- * @see Repository#update(UpdateRequest)
- */
@Override
- public ScimUser update(UpdateRequest<ScimUser> updateRequest) throws UnableToUpdateResourceException {
- String id = updateRequest.getId();
- ScimUser resource = updateRequest.getResource();
+ public ScimUser update(String id, String version, ScimUser resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
users.put(id, resource);
return resource;
}
+ @Override
+ public ScimUser patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ ScimUser resource = patchHandler.apply(get(id), patchOperations);
+ users.put(id, resource);
+ return resource;
+ }
+
+
/**
* @see Repository#get(java.lang.String)
*/
diff --git a/scim-server-examples/scim-server-memory/src/main/java/org/apache/directory/scim/example/memory/service/InMemoryGroupService.java b/scim-server-examples/scim-server-memory/src/main/java/org/apache/directory/scim/example/memory/service/InMemoryGroupService.java
index 599de035..f5de84a0 100644
--- a/scim-server-examples/scim-server-memory/src/main/java/org/apache/directory/scim/example/memory/service/InMemoryGroupService.java
+++ b/scim-server-examples/scim-server-memory/src/main/java/org/apache/directory/scim/example/memory/service/InMemoryGroupService.java
@@ -23,15 +23,17 @@ import jakarta.annotation.PostConstruct;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
+import org.apache.directory.scim.core.repository.PatchHandler;
import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
+import org.apache.directory.scim.spec.exception.ResourceException;
import org.apache.directory.scim.spec.filter.FilterExpressions;
import org.apache.directory.scim.spec.filter.FilterResponse;
import org.apache.directory.scim.spec.filter.Filter;
import org.apache.directory.scim.spec.filter.PageRequest;
import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
import org.apache.directory.scim.spec.resources.ScimExtension;
import org.apache.directory.scim.spec.resources.ScimGroup;
@@ -39,6 +41,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -54,9 +57,12 @@ public class InMemoryGroupService implements Repository<ScimGroup> {
private SchemaRegistry schemaRegistry;
+ private PatchHandler patchHandler;
+
@Inject
- public InMemoryGroupService(SchemaRegistry schemaRegistry) {
+ public InMemoryGroupService(SchemaRegistry schemaRegistry, PatchHandler patchHandler) {
this.schemaRegistry = schemaRegistry;
+ this.patchHandler = patchHandler;
}
protected InMemoryGroupService() {}
@@ -98,9 +104,14 @@ public class InMemoryGroupService implements Repository<ScimGroup> {
}
@Override
- public ScimGroup update(UpdateRequest<ScimGroup> updateRequest) throws UnableToUpdateResourceException {
- String id = updateRequest.getId();
- ScimGroup resource = updateRequest.getResource();
+ public ScimGroup update(String id, String version, ScimGroup resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ groups.put(id, resource);
+ return resource;
+ }
+
+ @Override
+ public ScimGroup patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ ScimGroup resource = patchHandler.apply(get(id), patchOperations);
groups.put(id, resource);
return resource;
}
diff --git a/scim-server-examples/scim-server-memory/src/main/java/org/apache/directory/scim/example/memory/service/InMemoryUserService.java b/scim-server-examples/scim-server-memory/src/main/java/org/apache/directory/scim/example/memory/service/InMemoryUserService.java
index 5bef2f5c..562cc662 100644
--- a/scim-server-examples/scim-server-memory/src/main/java/org/apache/directory/scim/example/memory/service/InMemoryUserService.java
+++ b/scim-server-examples/scim-server-memory/src/main/java/org/apache/directory/scim/example/memory/service/InMemoryUserService.java
@@ -22,6 +22,7 @@ package org.apache.directory.scim.example.memory.service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -31,17 +32,19 @@ import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.ws.rs.core.Response;
+import org.apache.directory.scim.core.repository.PatchHandler;
import org.apache.directory.scim.example.memory.extensions.LuckyNumberExtension;
import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
+import org.apache.directory.scim.spec.exception.ResourceException;
import org.apache.directory.scim.spec.extension.EnterpriseExtension;
import org.apache.directory.scim.spec.filter.FilterExpressions;
import org.apache.directory.scim.spec.filter.FilterResponse;
import org.apache.directory.scim.spec.filter.Filter;
import org.apache.directory.scim.spec.filter.PageRequest;
import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
import org.apache.directory.scim.spec.resources.*;
import org.apache.directory.scim.core.schema.SchemaRegistry;
@@ -66,9 +69,12 @@ public class InMemoryUserService implements Repository<ScimUser> {
private SchemaRegistry schemaRegistry;
+ private PatchHandler patchHandler;
+
@Inject
- public InMemoryUserService(SchemaRegistry schemaRegistry) {
+ public InMemoryUserService(SchemaRegistry schemaRegistry, PatchHandler patchHandler) {
this.schemaRegistry = schemaRegistry;
+ this.patchHandler = patchHandler;
}
protected InMemoryUserService() {}
@@ -130,13 +136,15 @@ public class InMemoryUserService implements Repository<ScimUser> {
return resource;
}
- /**
- * @see Repository#update(UpdateRequest)
- */
@Override
- public ScimUser update(UpdateRequest<ScimUser> updateRequest) throws UnableToUpdateResourceException {
- String id = updateRequest.getId();
- ScimUser resource = updateRequest.getResource();
+ public ScimUser update(String id, String version, ScimUser resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ users.put(id, resource);
+ return resource;
+ }
+
+ @Override
+ public ScimUser patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ ScimUser resource = patchHandler.apply(get(id), patchOperations);
users.put(id, resource);
return resource;
}
diff --git a/scim-server-examples/scim-server-quarkus/src/main/java/org/apache/directory/scim/example/quarkus/service/InMemoryGroupService.java b/scim-server-examples/scim-server-quarkus/src/main/java/org/apache/directory/scim/example/quarkus/service/InMemoryGroupService.java
index 486d0845..af28ae7d 100644
--- a/scim-server-examples/scim-server-quarkus/src/main/java/org/apache/directory/scim/example/quarkus/service/InMemoryGroupService.java
+++ b/scim-server-examples/scim-server-quarkus/src/main/java/org/apache/directory/scim/example/quarkus/service/InMemoryGroupService.java
@@ -23,15 +23,17 @@ import jakarta.annotation.PostConstruct;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
+import org.apache.directory.scim.core.repository.PatchHandler;
import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
+import org.apache.directory.scim.spec.exception.ResourceException;
import org.apache.directory.scim.spec.filter.FilterExpressions;
import org.apache.directory.scim.spec.filter.FilterResponse;
import org.apache.directory.scim.spec.filter.Filter;
import org.apache.directory.scim.spec.filter.PageRequest;
import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
import org.apache.directory.scim.spec.resources.ScimExtension;
import org.apache.directory.scim.spec.resources.ScimGroup;
@@ -39,6 +41,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -54,9 +57,12 @@ public class InMemoryGroupService implements Repository<ScimGroup> {
private SchemaRegistry schemaRegistry;
+ private PatchHandler patchHandler;
+
@Inject
- public InMemoryGroupService(SchemaRegistry schemaRegistry) {
+ public InMemoryGroupService(SchemaRegistry schemaRegistry, PatchHandler patchHandler) {
this.schemaRegistry = schemaRegistry;
+ this.patchHandler = patchHandler;
}
protected InMemoryGroupService() {}
@@ -98,9 +104,14 @@ public class InMemoryGroupService implements Repository<ScimGroup> {
}
@Override
- public ScimGroup update(UpdateRequest<ScimGroup> updateRequest) throws UnableToUpdateResourceException {
- String id = updateRequest.getId();
- ScimGroup resource = updateRequest.getResource();
+ public ScimGroup update(String id, String version, ScimGroup resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ groups.put(id, resource);
+ return resource;
+ }
+
+ @Override
+ public ScimGroup patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ ScimGroup resource = patchHandler.apply(get(id), patchOperations);
groups.put(id, resource);
return resource;
}
diff --git a/scim-server-examples/scim-server-quarkus/src/main/java/org/apache/directory/scim/example/quarkus/service/InMemoryUserService.java b/scim-server-examples/scim-server-quarkus/src/main/java/org/apache/directory/scim/example/quarkus/service/InMemoryUserService.java
index 154ca57d..b8498462 100644
--- a/scim-server-examples/scim-server-quarkus/src/main/java/org/apache/directory/scim/example/quarkus/service/InMemoryUserService.java
+++ b/scim-server-examples/scim-server-quarkus/src/main/java/org/apache/directory/scim/example/quarkus/service/InMemoryUserService.java
@@ -19,10 +19,7 @@
package org.apache.directory.scim.example.quarkus.service;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
+import java.util.*;
import java.util.stream.Collectors;
import jakarta.annotation.PostConstruct;
@@ -31,17 +28,19 @@ import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.ws.rs.core.Response;
+import org.apache.directory.scim.core.repository.PatchHandler;
import org.apache.directory.scim.example.quarkus.extensions.LuckyNumberExtension;
import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
+import org.apache.directory.scim.spec.exception.ResourceException;
import org.apache.directory.scim.spec.extension.EnterpriseExtension;
import org.apache.directory.scim.spec.filter.FilterExpressions;
import org.apache.directory.scim.spec.filter.FilterResponse;
import org.apache.directory.scim.spec.filter.Filter;
import org.apache.directory.scim.spec.filter.PageRequest;
import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
import org.apache.directory.scim.spec.resources.*;
import org.apache.directory.scim.core.schema.SchemaRegistry;
@@ -66,9 +65,12 @@ public class InMemoryUserService implements Repository<ScimUser> {
private SchemaRegistry schemaRegistry;
+ private PatchHandler patchHandler;
+
@Inject
- public InMemoryUserService(SchemaRegistry schemaRegistry) {
+ public InMemoryUserService(SchemaRegistry schemaRegistry, PatchHandler patchHandler) {
this.schemaRegistry = schemaRegistry;
+ this.patchHandler = patchHandler;
}
protected InMemoryUserService() {}
@@ -130,13 +132,15 @@ public class InMemoryUserService implements Repository<ScimUser> {
return resource;
}
- /**
- * @see Repository#update(UpdateRequest)
- */
@Override
- public ScimUser update(UpdateRequest<ScimUser> updateRequest) throws UnableToUpdateResourceException {
- String id = updateRequest.getId();
- ScimUser resource = updateRequest.getResource();
+ public ScimUser update(String id, String version, ScimUser resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ users.put(id, resource);
+ return resource;
+ }
+
+ @Override
+ public ScimUser patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ ScimUser resource = patchHandler.apply(get(id), patchOperations);
users.put(id, resource);
return resource;
}
diff --git a/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryGroupService.java b/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryGroupService.java
index 5498f6fd..0df2da7b 100644
--- a/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryGroupService.java
+++ b/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryGroupService.java
@@ -21,12 +21,14 @@ package org.apache.directory.scim.example.spring.service;
import jakarta.annotation.PostConstruct;
import jakarta.ws.rs.core.Response;
+import org.apache.directory.scim.core.repository.PatchHandler;
import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
import org.apache.directory.scim.core.schema.SchemaRegistry;
import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
+import org.apache.directory.scim.spec.exception.ResourceException;
import org.apache.directory.scim.spec.filter.*;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
import org.apache.directory.scim.spec.resources.ScimExtension;
import org.apache.directory.scim.spec.resources.ScimGroup;
@@ -34,6 +36,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -47,8 +50,11 @@ public class InMemoryGroupService implements Repository<ScimGroup> {
private final SchemaRegistry schemaRegistry;
- public InMemoryGroupService(SchemaRegistry schemaRegistry) {
+ private final PatchHandler patchHandler;
+
+ public InMemoryGroupService(SchemaRegistry schemaRegistry, PatchHandler patchHandler) {
this.schemaRegistry = schemaRegistry;
+ this.patchHandler = patchHandler;
}
@PostConstruct
@@ -88,9 +94,14 @@ public class InMemoryGroupService implements Repository<ScimGroup> {
}
@Override
- public ScimGroup update(UpdateRequest<ScimGroup> updateRequest) throws UnableToUpdateResourceException {
- String id = updateRequest.getId();
- ScimGroup resource = updateRequest.getResource();
+ public ScimGroup update(String id, String version, ScimGroup resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ groups.put(id, resource);
+ return resource;
+ }
+
+ @Override
+ public ScimGroup patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ ScimGroup resource = patchHandler.apply(get(id), patchOperations);
groups.put(id, resource);
return resource;
}
diff --git a/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryUserService.java b/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryUserService.java
index dca649c8..36ba0cc7 100644
--- a/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryUserService.java
+++ b/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryUserService.java
@@ -22,20 +22,23 @@ package org.apache.directory.scim.example.spring.service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import jakarta.annotation.PostConstruct;
import jakarta.ws.rs.core.Response;
+import org.apache.directory.scim.core.repository.PatchHandler;
import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
import org.apache.directory.scim.core.schema.SchemaRegistry;
import org.apache.directory.scim.example.spring.extensions.LuckyNumberExtension;
import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
+import org.apache.directory.scim.spec.exception.ResourceException;
import org.apache.directory.scim.spec.extension.EnterpriseExtension;
import org.apache.directory.scim.spec.filter.*;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
import org.apache.directory.scim.spec.resources.*;
import org.springframework.stereotype.Service;
@@ -59,8 +62,11 @@ public class InMemoryUserService implements Repository<ScimUser> {
private final SchemaRegistry schemaRegistry;
- public InMemoryUserService(SchemaRegistry schemaRegistry) {
+ private final PatchHandler patchHandler;
+
+ public InMemoryUserService(SchemaRegistry schemaRegistry, PatchHandler patchHandler) {
this.schemaRegistry = schemaRegistry;
+ this.patchHandler = patchHandler;
}
@PostConstruct
@@ -120,13 +126,15 @@ public class InMemoryUserService implements Repository<ScimUser> {
return resource;
}
- /**
- * @see Repository#update(UpdateRequest)
- */
@Override
- public ScimUser update(UpdateRequest<ScimUser> updateRequest) throws UnableToUpdateResourceException {
- String id = updateRequest.getId();
- ScimUser resource = updateRequest.getResource();
+ public ScimUser update(String id, String version, ScimUser resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ users.put(id, resource);
+ return resource;
+ }
+
+ @Override
+ public ScimUser patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ ScimUser resource = patchHandler.apply(get(id), patchOperations);
users.put(id, resource);
return resource;
}
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 39dd8260..e16d8959 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
@@ -25,10 +25,8 @@ 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;
import jakarta.ws.rs.core.*;
import jakarta.ws.rs.core.Response.ResponseBuilder;
import jakarta.ws.rs.core.Response.Status;
@@ -40,10 +38,10 @@ 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.apache.directory.scim.spec.schema.Meta;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.apache.directory.scim.core.repository.UpdateRequest;
import org.apache.directory.scim.core.repository.annotations.ScimProcessingExtension;
import org.apache.directory.scim.core.repository.extensions.AttributeFilterExtension;
import org.apache.directory.scim.core.repository.extensions.ProcessingExtension;
@@ -53,7 +51,6 @@ import org.apache.directory.scim.protocol.adapter.FilterWrapper;
import org.apache.directory.scim.protocol.BaseResourceTypeResource;
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;
import org.apache.directory.scim.protocol.data.ListResponse;
import org.apache.directory.scim.protocol.data.PatchRequest;
import org.apache.directory.scim.protocol.data.SearchRequest;
@@ -70,14 +67,10 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
private static final Logger LOG = LoggerFactory.getLogger(BaseResourceTypeResourceImpl.class);
- private final SchemaRegistry schemaRegistry;
-
private final RepositoryRegistry repositoryRegistry;
private final AttributeUtil attributeUtil;
- private final EtagGenerator etagGenerator;
-
private final Class<T> resourceClass;
// TODO: Field injection of UriInfo, Request should work with all implementations
@@ -88,10 +81,11 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
@Context
Request request;
- public BaseResourceTypeResourceImpl(SchemaRegistry schemaRegistry, RepositoryRegistry repositoryRegistry, EtagGenerator etagGenerator, Class<T> resourceClass) {
- this.schemaRegistry = schemaRegistry;
+ @Context
+ HttpHeaders headers;
+
+ public BaseResourceTypeResourceImpl(SchemaRegistry schemaRegistry, RepositoryRegistry repositoryRegistry, Class<T> resourceClass) {
this.repositoryRegistry = repositoryRegistry;
- this.etagGenerator = etagGenerator;
this.resourceClass = resourceClass;
this.attributeUtil = new AttributeUtil(schemaRegistry);
}
@@ -126,10 +120,13 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
}
}
- if (resource != null) {
- EntityTag backingETag = requireEtag(resource);
- ResponseBuilder evaluatePreconditionsResponse = request.evaluatePreconditions(backingETag);
+ if (resource == null) {
+ throw notFoundException(id);
+ }
+ EntityTag etag = fromVersion(resource);
+ if (etag != null) {
+ ResponseBuilder evaluatePreconditionsResponse = request.evaluatePreconditions(etag);
if (evaluatePreconditionsResponse != null) {
return Response.status(Status.NOT_MODIFIED).build();
}
@@ -139,12 +136,6 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
Set<AttributeReference> excludedAttributeReferences = AttributeReferenceListWrapper.getAttributeReferences(excludedAttributes);
validateAttributes(attributeReferences, excludedAttributeReferences);
- if (resource == null) {
- throw notFoundException(id);
- }
-
- EntityTag etag = requireEtag(resource);
-
// Process Attributes
resource = processFilterAttributeExtensions(repository, resource, attributeReferences, excludedAttributeReferences);
resource = attributesForDisplayThrowOnError(resource, attributeReferences, excludedAttributeReferences);
@@ -186,7 +177,7 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
T created = repository.create(resource);
- EntityTag etag = etag(created);
+ EntityTag etag = fromVersion(created);
// Process Attributes
created = processFilterAttributeExtensions(repository, created, attributeReferences, excludedAttributeReferences);
@@ -195,10 +186,6 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
created = attributesForDisplay(created, attributeReferences, excludedAttributeReferences);
} catch (AttributeException e) {
log.debug("Exception thrown while processing attributes", e);
- return Response.status(Status.CREATED)
- .location(buildLocationTag(created))
- .tag(etag)
- .build();
}
return Response.status(Status.CREATED)
@@ -244,7 +231,6 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
List<T> results = new ArrayList<>();
for (T resource : filterResp.getResources()) {
- requireEtag(resource);
// Process Attributes
resource = processFilterAttributeExtensions(repository, resource, attributeReferences, excludedAttributeReferences);
@@ -262,15 +248,14 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
@Override
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));
+ return update(attributes, excludedAttributes, (etag, includeAttributes, excludeAttributes, repository)
+ -> repository.update(id, etag,resource, includeAttributes, excludeAttributes));
}
@Override
public Response patch(PatchRequest patchRequest, String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException, ResourceException {
-
- return update(id, attributes, excludedAttributes, (stored) ->
- new UpdateRequest<>(id, stored, patchRequest.getPatchOperationList(), schemaRegistry));
+ return update(attributes, excludedAttributes, (etag, includeAttributes, excludeAttributes, repository)
+ -> repository.patch(id, etag, patchRequest.getPatchOperationList(), includeAttributes, excludeAttributes));
}
@Override
@@ -281,7 +266,7 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
.build();
}
- private Response update(String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes, Function<T, UpdateRequest<T>> updateRequestFunction) throws ScimException, ResourceException {
+ private Response update(AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes, UpdateFunction<T> updateFunction) throws ScimException, ResourceException {
Repository<T> repository = getRepositoryInternal();
@@ -289,23 +274,14 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
Set<AttributeReference> excludedAttributeReferences = AttributeReferenceListWrapper.getAttributeReferences(excludedAttributes);
validateAttributes(attributeReferences, excludedAttributeReferences);
- T stored = repository.get(id);
-
- if (stored == null) {
- throw notFoundException(id);
- }
-
- EntityTag backingETag = requireEtag(stored);
- validatePreconditions(id, backingETag);
-
- UpdateRequest<T> updateRequest = updateRequestFunction.apply(stored);
- T updated = repository.update(updateRequest);
+ String requestEtag = headers.getHeaderString("ETag");
+ T updated = updateFunction.update(requestEtag, attributeReferences, excludedAttributeReferences, repository);
// Process Attributes
updated = processFilterAttributeExtensions(repository, updated, attributeReferences, excludedAttributeReferences);
updated = attributesForDisplayIgnoreErrors(updated, attributeReferences, excludedAttributeReferences);
- EntityTag etag = etag(updated);
+ EntityTag etag = fromVersion(updated);
return Response.ok(updated)
.location(buildLocationTag(updated))
.tag(etag)
@@ -382,35 +358,25 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
return new ScimException(Status.NOT_FOUND, "Resource " + id + " not found");
}
- private void validatePreconditions(String id, EntityTag etag) {
- ResponseBuilder response = request.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 EntityTag requireEtag(ScimResource resource) throws ScimException {
- try {
- return etagGenerator.generateEtag(resource);
- } catch (EtagGenerationException e) {
- throw new ScimException(Status.INTERNAL_SERVER_ERROR, "Failed to generate the etag", e);
+ 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");
}
}
- private EntityTag etag(ScimResource resource) {
- try {
- return etagGenerator.generateEtag(resource);
- } catch (EtagGenerationException e) {
- log.warn("Failed to generate etag for resource", e);
- return null;
+ private EntityTag fromVersion(ScimResource resource) {
+ Meta meta = resource.getMeta();
+ if (meta != null) {
+ String version = meta.getVersion();
+ if (version != null) {
+ return new EntityTag(version);
+ }
}
+ return null;
}
- 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");
- }
+ @FunctionalInterface
+ private interface UpdateFunction<T extends ScimResource> {
+ T update(String etag, Set<AttributeReference> includeAttributes, Set<AttributeReference> excludeAttributes, Repository<T> repository) throws ResourceException;
}
}
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/rest/BulkResourceImpl.java b/scim-server/src/main/java/org/apache/directory/scim/server/rest/BulkResourceImpl.java
index 4d1291a0..72c9155e 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/rest/BulkResourceImpl.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/rest/BulkResourceImpl.java
@@ -36,7 +36,6 @@ import org.apache.directory.scim.server.exception.UnableToRetrieveResourceExcept
import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
import org.apache.directory.scim.core.repository.Repository;
import org.apache.directory.scim.core.repository.RepositoryRegistry;
-import org.apache.directory.scim.core.repository.UpdateRequest;
import org.apache.directory.scim.protocol.BulkResource;
import org.apache.directory.scim.protocol.data.BulkOperation;
import org.apache.directory.scim.protocol.data.BulkOperation.Method;
@@ -288,10 +287,7 @@ public class BulkResourceImpl implements BulkResource {
Class<ScimResource> scimResourceClass = (Class<ScimResource>) scimResource.getClass();
Repository<ScimResource> repository = repositoryRegistry.getRepository(scimResourceClass);
- ScimResource original = repository.get(scimResourceId);
-
- UpdateRequest<ScimResource> updateRequest = new UpdateRequest<>(scimResourceId, original, scimResource, schemaRegistry);
- repository.update(updateRequest);
+ repository.update(scimResourceId, null, scimResource, Collections.emptySet(), Collections.emptySet());
} catch (UnresolvableOperationException unresolvableOperationException) {
log.error("Could not complete final resolution pass, unresolvable bulkId", unresolvableOperationException);
@@ -464,11 +460,7 @@ public class BulkResourceImpl implements BulkResource {
+ 1);
try {
- ScimResource original = repository.get(id);
-
- UpdateRequest<ScimResource> updateRequest = new UpdateRequest<>(id, original, scimResource, schemaRegistry);
-
- repository.update(updateRequest);
+ repository.update(id, null, scimResource, Collections.emptySet(), Collections.emptySet());
operationResult.setStatus(StatusWrapper.wrap(Status.OK));
} catch (UnableToRetrieveResourceException e) {
operationResult.setStatus(StatusWrapper.wrap(Status.NOT_FOUND));
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/rest/GroupResourceImpl.java b/scim-server/src/main/java/org/apache/directory/scim/server/rest/GroupResourceImpl.java
index 1384204c..bcdeb1e1 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/rest/GroupResourceImpl.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/rest/GroupResourceImpl.java
@@ -35,12 +35,12 @@ import org.apache.directory.scim.core.schema.SchemaRegistry;
public class GroupResourceImpl extends BaseResourceTypeResourceImpl<ScimGroup> implements GroupResource {
@Inject
- public GroupResourceImpl(SchemaRegistry schemaRegistry, RepositoryRegistry repositoryRegistry, EtagGenerator etagGenerator) {
- super(schemaRegistry, repositoryRegistry, etagGenerator, ScimGroup.class);
+ public GroupResourceImpl(SchemaRegistry schemaRegistry, RepositoryRegistry repositoryRegistry) {
+ super(schemaRegistry, repositoryRegistry, ScimGroup.class);
}
public GroupResourceImpl() {
// CDI
- this(null, null, null);
+ this(null, null);
}
}
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/rest/UserResourceImpl.java b/scim-server/src/main/java/org/apache/directory/scim/server/rest/UserResourceImpl.java
index 75bebe21..963f43ca 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/rest/UserResourceImpl.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/rest/UserResourceImpl.java
@@ -39,12 +39,12 @@ import org.apache.directory.scim.core.schema.SchemaRegistry;
public class UserResourceImpl extends BaseResourceTypeResourceImpl<ScimUser> implements UserResource {
@Inject
- public UserResourceImpl(SchemaRegistry schemaRegistry, RepositoryRegistry repositoryRegistry, EtagGenerator etagGenerator) {
- super(schemaRegistry, repositoryRegistry, etagGenerator, ScimUser.class);
+ public UserResourceImpl(SchemaRegistry schemaRegistry, RepositoryRegistry repositoryRegistry) {
+ super(schemaRegistry, repositoryRegistry, ScimUser.class);
}
public UserResourceImpl() {
// CDI
- this(null, null, null);
+ this(null, null);
}
}
diff --git a/scim-server/src/test/java/org/apache/directory/scim/server/it/testapp/InMemoryGroupService.java b/scim-server/src/test/java/org/apache/directory/scim/server/it/testapp/InMemoryGroupService.java
index b4de6ee0..2dac3ead 100644
--- a/scim-server/src/test/java/org/apache/directory/scim/server/it/testapp/InMemoryGroupService.java
+++ b/scim-server/src/test/java/org/apache/directory/scim/server/it/testapp/InMemoryGroupService.java
@@ -25,24 +25,22 @@ import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
+import org.apache.directory.scim.core.repository.PatchHandler;
import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
+import org.apache.directory.scim.spec.exception.ResourceException;
import org.apache.directory.scim.spec.filter.FilterExpressions;
import org.apache.directory.scim.spec.filter.FilterResponse;
import org.apache.directory.scim.spec.filter.Filter;
import org.apache.directory.scim.spec.filter.PageRequest;
import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
import org.apache.directory.scim.spec.resources.ScimExtension;
import org.apache.directory.scim.spec.resources.ScimGroup;
import org.apache.directory.scim.core.schema.SchemaRegistry;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
+import java.util.*;
import java.util.stream.Collectors;
@Named
@@ -53,9 +51,12 @@ public class InMemoryGroupService implements Repository<ScimGroup> {
private SchemaRegistry schemaRegistry;
+ private PatchHandler patchHandler;
+
@Inject
- public InMemoryGroupService(SchemaRegistry schemaRegistry) {
+ public InMemoryGroupService(SchemaRegistry schemaRegistry, PatchHandler patchHandler) {
this.schemaRegistry = schemaRegistry;
+ this.patchHandler = patchHandler;
}
protected InMemoryGroupService() {}
@@ -97,9 +98,14 @@ public class InMemoryGroupService implements Repository<ScimGroup> {
}
@Override
- public ScimGroup update(UpdateRequest<ScimGroup> updateRequest) throws UnableToUpdateResourceException {
- String id = updateRequest.getId();
- ScimGroup resource = updateRequest.getResource();
+ public ScimGroup update(String id, String version, ScimGroup resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ groups.put(id, resource);
+ return resource;
+ }
+
+ @Override
+ public ScimGroup patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ ScimGroup resource = patchHandler.apply(get(id), patchOperations);
groups.put(id, resource);
return resource;
}
diff --git a/scim-server/src/test/java/org/apache/directory/scim/server/it/testapp/InMemoryUserService.java b/scim-server/src/test/java/org/apache/directory/scim/server/it/testapp/InMemoryUserService.java
index f9d29f3c..909df75b 100644
--- a/scim-server/src/test/java/org/apache/directory/scim/server/it/testapp/InMemoryUserService.java
+++ b/scim-server/src/test/java/org/apache/directory/scim/server/it/testapp/InMemoryUserService.java
@@ -24,21 +24,24 @@ import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.ws.rs.core.Response;
+import org.apache.directory.scim.core.repository.PatchHandler;
import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
+import org.apache.directory.scim.spec.exception.ResourceException;
import org.apache.directory.scim.spec.filter.FilterExpressions;
import org.apache.directory.scim.spec.filter.FilterResponse;
import org.apache.directory.scim.spec.filter.Filter;
import org.apache.directory.scim.spec.filter.PageRequest;
import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
import org.apache.directory.scim.spec.resources.*;
import org.apache.directory.scim.core.schema.SchemaRegistry;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -62,9 +65,12 @@ public class InMemoryUserService implements Repository<ScimUser> {
private SchemaRegistry schemaRegistry;
+ private PatchHandler patchHandler;
+
@Inject
- public InMemoryUserService(SchemaRegistry schemaRegistry) {
+ public InMemoryUserService(SchemaRegistry schemaRegistry, PatchHandler patchHandler) {
this.schemaRegistry = schemaRegistry;
+ this.patchHandler = patchHandler;
}
protected InMemoryUserService() {}
@@ -123,13 +129,15 @@ public class InMemoryUserService implements Repository<ScimUser> {
return resource;
}
- /**
- * @see Repository#update(UpdateRequest)
- */
@Override
- public ScimUser update(UpdateRequest<ScimUser> updateRequest) throws UnableToUpdateResourceException {
- String id = updateRequest.getId();
- ScimUser resource = updateRequest.getResource();
+ public ScimUser update(String id, String version, ScimUser resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ users.put(id, resource);
+ return resource;
+ }
+
+ @Override
+ public ScimUser patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ ScimUser resource = patchHandler.apply(get(id), patchOperations);
users.put(id, resource);
return resource;
}
diff --git a/support/spring-boot/src/main/java/org/apache/directory/scim/spring/ScimpleSpringConfiguration.java b/support/spring-boot/src/main/java/org/apache/directory/scim/spring/ScimpleSpringConfiguration.java
index 898c8332..a976a330 100644
--- a/support/spring-boot/src/main/java/org/apache/directory/scim/spring/ScimpleSpringConfiguration.java
+++ b/support/spring-boot/src/main/java/org/apache/directory/scim/spring/ScimpleSpringConfiguration.java
@@ -23,6 +23,8 @@ import jakarta.enterprise.inject.Instance;
import jakarta.enterprise.util.TypeLiteral;
import jakarta.ws.rs.core.Application;
import org.apache.commons.lang3.NotImplementedException;
+import org.apache.directory.scim.core.repository.DefaultPatchHandler;
+import org.apache.directory.scim.core.repository.PatchHandler;
import org.apache.directory.scim.core.repository.Repository;
import org.apache.directory.scim.core.repository.RepositoryRegistry;
import org.apache.directory.scim.core.repository.SelfIdResolver;
@@ -73,6 +75,12 @@ public class ScimpleSpringConfiguration {
return new SchemaRegistry();
}
+ @Bean
+ @ConditionalOnMissingBean
+ PatchHandler patchHandler(SchemaRegistry schemaRegistry) {
+ return new DefaultPatchHandler(schemaRegistry);
+ }
+
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(SelfIdResolver.class)
diff --git a/support/spring-boot/src/test/java/org/apache/directory/scim/spring/it/app/InMemoryGroupService.java b/support/spring-boot/src/test/java/org/apache/directory/scim/spring/it/app/InMemoryGroupService.java
index 688367f1..07a79d03 100644
--- a/support/spring-boot/src/test/java/org/apache/directory/scim/spring/it/app/InMemoryGroupService.java
+++ b/support/spring-boot/src/test/java/org/apache/directory/scim/spring/it/app/InMemoryGroupService.java
@@ -22,12 +22,14 @@ package org.apache.directory.scim.spring.it.app;
import jakarta.annotation.PostConstruct;
import jakarta.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
+import org.apache.directory.scim.core.repository.PatchHandler;
import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
import org.apache.directory.scim.core.schema.SchemaRegistry;
import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
+import org.apache.directory.scim.spec.exception.ResourceException;
import org.apache.directory.scim.spec.filter.*;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
import org.apache.directory.scim.spec.resources.ScimExtension;
import org.apache.directory.scim.spec.resources.ScimGroup;
import org.springframework.stereotype.Service;
@@ -36,6 +38,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -46,8 +49,11 @@ public class InMemoryGroupService implements Repository<ScimGroup> {
private final SchemaRegistry schemaRegistry;
- public InMemoryGroupService(SchemaRegistry schemaRegistry) {
+ private final PatchHandler patchHandler;
+
+ public InMemoryGroupService(SchemaRegistry schemaRegistry, PatchHandler patchHandler) {
this.schemaRegistry = schemaRegistry;
+ this.patchHandler = patchHandler;
}
@PostConstruct
@@ -87,9 +93,14 @@ public class InMemoryGroupService implements Repository<ScimGroup> {
}
@Override
- public ScimGroup update(UpdateRequest<ScimGroup> updateRequest) throws UnableToUpdateResourceException {
- String id = updateRequest.getId();
- ScimGroup resource = updateRequest.getResource();
+ public ScimGroup update(String id, String version, ScimGroup resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ groups.put(id, resource);
+ return resource;
+ }
+
+ @Override
+ public ScimGroup patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ ScimGroup resource = patchHandler.apply(get(id), patchOperations);
groups.put(id, resource);
return resource;
}
diff --git a/support/spring-boot/src/test/java/org/apache/directory/scim/spring/it/app/InMemoryUserService.java b/support/spring-boot/src/test/java/org/apache/directory/scim/spring/it/app/InMemoryUserService.java
index 51b50b64..430d837f 100644
--- a/support/spring-boot/src/test/java/org/apache/directory/scim/spring/it/app/InMemoryUserService.java
+++ b/support/spring-boot/src/test/java/org/apache/directory/scim/spring/it/app/InMemoryUserService.java
@@ -21,12 +21,14 @@ package org.apache.directory.scim.spring.it.app;
import jakarta.annotation.PostConstruct;
import jakarta.ws.rs.core.Response;
+import org.apache.directory.scim.core.repository.PatchHandler;
import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
import org.apache.directory.scim.core.schema.SchemaRegistry;
import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
+import org.apache.directory.scim.spec.exception.ResourceException;
import org.apache.directory.scim.spec.filter.*;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
import org.apache.directory.scim.spec.resources.*;
import org.springframework.stereotype.Service;
@@ -34,6 +36,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -56,8 +59,11 @@ public class InMemoryUserService implements Repository<ScimUser> {
private final SchemaRegistry schemaRegistry;
- public InMemoryUserService(SchemaRegistry schemaRegistry) {
+ private final PatchHandler patchHandler;
+
+ public InMemoryUserService(SchemaRegistry schemaRegistry, PatchHandler patchHandler) {
this.schemaRegistry = schemaRegistry;
+ this.patchHandler = patchHandler;
}
@PostConstruct
@@ -117,13 +123,15 @@ public class InMemoryUserService implements Repository<ScimUser> {
return resource;
}
- /**
- * @see Repository#update(UpdateRequest)
- */
@Override
- public ScimUser update(UpdateRequest<ScimUser> updateRequest) throws UnableToUpdateResourceException {
- String id = updateRequest.getId();
- ScimUser resource = updateRequest.getResource();
+ public ScimUser update(String id, String version, ScimUser resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ users.put(id, resource);
+ return resource;
+ }
+
+ @Override
+ public ScimUser patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+ ScimUser resource = patchHandler.apply(get(id), patchOperations);
users.put(id, resource);
return resource;
}