
de.captaingoldfish.scim.sdk.server.patch.PatchHandler Maven / Gradle / Ivy
// Generated by delombok at Thu Nov 02 20:38:53 CET 2023
package de.captaingoldfish.scim.sdk.server.patch;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.TextNode;
import de.captaingoldfish.scim.sdk.common.constants.ScimType;
import de.captaingoldfish.scim.sdk.common.constants.enums.PatchOp;
import de.captaingoldfish.scim.sdk.common.exceptions.BadRequestException;
import de.captaingoldfish.scim.sdk.common.exceptions.InvalidFilterException;
import de.captaingoldfish.scim.sdk.common.request.PatchOpRequest;
import de.captaingoldfish.scim.sdk.common.request.PatchRequestOperation;
import de.captaingoldfish.scim.sdk.common.resources.ResourceNode;
import de.captaingoldfish.scim.sdk.common.resources.base.ScimObjectNode;
import de.captaingoldfish.scim.sdk.common.resources.complex.Meta;
import de.captaingoldfish.scim.sdk.common.resources.complex.PatchConfig;
import de.captaingoldfish.scim.sdk.common.schemas.SchemaAttribute;
import de.captaingoldfish.scim.sdk.common.utils.JsonHelper;
import de.captaingoldfish.scim.sdk.server.filter.AttributePathRoot;
import de.captaingoldfish.scim.sdk.server.patch.msazure.MsAzurePatchAttributeRebuilder;
import de.captaingoldfish.scim.sdk.server.patch.msazure.MsAzurePatchRemoveRebuilder;
import de.captaingoldfish.scim.sdk.server.patch.msazure.MsAzurePatchValueSubAttributeRebuilder;
import de.captaingoldfish.scim.sdk.server.schemas.ResourceType;
import de.captaingoldfish.scim.sdk.server.utils.RequestUtils;
/**
* author Pascal Knueppel
* created at: 29.10.2019 - 09:40
*
* this class is used to resolve patch operations on resources
*/
public class PatchHandler
{
@java.lang.SuppressWarnings("all")
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PatchHandler.class);
/**
* the current patch configuration of the service provider
*/
private final PatchConfig patchConfig;
/**
* this resource type is used to get the attribute definitions of the values from the patch operations
*/
private final ResourceType resourceType;
/**
* this object will hold the attributes that have been present within the operations. This is used to return
* these attributes in requests in which the attributes parameter was present.
*/
private final ScimObjectNode requestedAttributes;
/**
* this attribute tells us if the resource was effectively changed meaning that an attribute did receive a new
* value that differs from the value before
*/
private boolean changedResource;
public PatchHandler(PatchConfig patchConfig, ResourceType resourceType)
{
this.patchConfig = patchConfig;
this.requestedAttributes = new ScimObjectNode();
this.resourceType = Objects.requireNonNull(resourceType);
}
/**
* this method will execute the patch operation on the given resource
*
* @param resource the resource representation that should be patched
* @param patchOpRequest the patch operation that should be executed on the resource
* @return the patched resource
*/
public T patchResource(T resource, PatchOpRequest patchOpRequest)
{
AtomicBoolean changeWasMade = new AtomicBoolean(false);
for ( PatchRequestOperation operation : patchOpRequest.getOperations() )
{
changeWasMade.compareAndSet(false, handlePatchOp(resource, operation));
}
setLastModified(resource, changeWasMade);
changedResource = changeWasMade.get();
return resource;
}
/**
* adds the attributes to the {@link #requestedAttributes} object node to return the requested attributes
* object. This is necessary for schema validation so that the changed attributes are returned on the response
* if the attributes parameter was used in the request
*
* @param operation the operation that tells us if this is a remove operation or not
* @param path the path expression that is necessary to determine the attributes that have been modified
*/
private void setAttributeFromPath(PatchRequestOperation operation, AttributePathRoot path)
{
boolean isExtensionNodeReference = path instanceof PatchExtensionAttributePath;
if (operation.getOp().equals(PatchOp.REMOVE) || isExtensionNodeReference)
{
// in this case the attribute is not present anymore so there is nothing to return
// an extension node reference is a simple path and not a direct attribute reference, so it cannot be resolved
// to a schema attribute
return;
}
String fullName = path.getFullName() + (path.getSubAttributeName() == null ? "" : "." + path.getSubAttributeName());
SchemaAttribute schemaAttribute = RequestUtils.getSchemaAttributeByAttributeName(resourceType, fullName);
if (schemaAttribute.getParent() == null)
{
requestedAttributes.set(schemaAttribute.getName(), new TextNode(""));
}
else
{
ScimObjectNode objectNode = new ScimObjectNode();
objectNode.set(schemaAttribute.getName(), new TextNode(""));
requestedAttributes.set(schemaAttribute.getParent().getName(), objectNode);
}
}
/**
* adds the attributes to the {@link #requestedAttributes} object node to return the requested attributes
* object. This is necessary for schema validation so that the changed attributes are returned on the response
* if the attributes parameter was used in the request
*
* @param operation the operation that tells us which attributes were requested to change
*/
private void setAttributesFromResource(PatchRequestOperation operation)
{
if (operation.getOp().equals(PatchOp.REMOVE))
{
// in this case the attribute is not present anymore so there is nothing to return
return;
}
JsonNode resource = JsonHelper.readJsonDocument(operation.getValues().get(0));
resource.fields().forEachRemaining(stringJsonNodeEntry -> {
requestedAttributes.set(stringJsonNodeEntry.getKey(), stringJsonNodeEntry.getValue());
});
}
/**
* will add the given attributes to the given resource
*
* @param resource the resource to which the attributes should be added
* @param operation the operation request that contains the new attributes
*/
private boolean handlePatchOp(ResourceNode resource, PatchRequestOperation operation)
{
Optional target = operation.getPath();
List values = operation.getValues();
if (!operation.getOp().equals(PatchOp.REMOVE) && (values == null || values.isEmpty()))
{
throw new BadRequestException("no value attributes present in patch operation", null,
ScimType.RFC7644.INVALID_VALUE);
}
if (target.isPresent())
{
String path = target.get();
if (PatchOp.REMOVE.equals(operation.getOp()))
{
MsAzurePatchRemoveRebuilder msAzureWorkaround = new MsAzurePatchRemoveRebuilder(operation.getOp(), path,
values);
path = msAzureWorkaround.fixPath();
}
if (patchConfig.isMsAzureValueSubAttributeWorkaroundActive())
{
MsAzurePatchValueSubAttributeRebuilder msAzureWorkaround;
msAzureWorkaround = new MsAzurePatchValueSubAttributeRebuilder(operation.getOp(), values);
values = msAzureWorkaround.fixValues();
}
PatchTargetHandler patchTargetHandler;
try
{
patchTargetHandler = new PatchTargetHandler(patchConfig, resourceType, operation.getOp(), path);
}
catch (InvalidFilterException ex)
{
if (patchConfig.isIgnoreUnknownAttribute() && ScimType.RFC7644.INVALID_PATH.equals(ex.getScimType()))
{
return false;
}
else
{
throw ex;
}
}
boolean changeWasMade = patchTargetHandler.handleOperationValues(resource, values);
setAttributeFromPath(operation, patchTargetHandler.getPath());
return changeWasMade;
}
else
{
if (PatchOp.REMOVE.equals(operation.getOp()))
{
throw new BadRequestException("missing target for remove operation", null, ScimType.RFC7644.NO_TARGET);
}
if (values.size() > 1)
{
throw new BadRequestException("too many resources set in patch operation. If the target is not specified only"
+ " a single value must be present in the values list which represents the "
+ "resource itself", null, ScimType.RFC7644.INVALID_VALUE);
}
boolean executeMsAzureWorkaround = PatchOp.ADD.equals(operation.getOp())
|| PatchOp.REPLACE.equals(operation.getOp());
if (executeMsAzureWorkaround)
{
MsAzurePatchAttributeRebuilder msAzureWorkaround = new MsAzurePatchAttributeRebuilder(operation.getOp(), values,
resourceType);
values = msAzureWorkaround.fixValues();
}
PatchResourceHandler patchResourceHandler = new PatchResourceHandler(patchConfig, resourceType,
operation.getOp());
boolean changeWasMade = patchResourceHandler.addResourceValues(resource,
JsonHelper.readJsonDocument(values.get(0)),
null);
setAttributesFromResource(operation);
return changeWasMade;
}
}
/**
* overrides the lastModified value if a change was made
*
* @param resource the resource of which the lastModified value should be changed
* @param changeWasMade if the lastModified value should be changed or not
*/
private void setLastModified(ResourceNode resource, AtomicBoolean changeWasMade)
{
if (changeWasMade.get())
{
Optional metaOptional = resource.getMeta();
if (metaOptional.isPresent())
{
metaOptional.get().setLastModified(LocalDateTime.now());
}
else
{
Meta meta = Meta.builder().lastModified(LocalDateTime.now()).build();
resource.setMeta(meta);
}
}
}
/**
* this object will hold the attributes that have been present within the operations. This is used to return
* these attributes in requests in which the attributes parameter was present.
*/
@java.lang.SuppressWarnings("all")
public ScimObjectNode getRequestedAttributes()
{
return this.requestedAttributes;
}
/**
* this attribute tells us if the resource was effectively changed meaning that an attribute did receive a new
* value that differs from the value before
*/
@java.lang.SuppressWarnings("all")
public boolean isChangedResource()
{
return this.changedResource;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy