
de.captaingoldfish.scim.sdk.server.endpoints.bulkid.BulkIdResolver Maven / Gradle / Ivy
package de.captaingoldfish.scim.sdk.server.endpoints.bulkid;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import de.captaingoldfish.scim.sdk.common.constants.ScimType;
import de.captaingoldfish.scim.sdk.common.constants.enums.HttpMethod;
import de.captaingoldfish.scim.sdk.common.exceptions.BadRequestException;
import de.captaingoldfish.scim.sdk.common.request.PatchOpRequest;
import de.captaingoldfish.scim.sdk.common.resources.base.ScimObjectNode;
import de.captaingoldfish.scim.sdk.common.utils.JsonHelper;
import de.captaingoldfish.scim.sdk.server.utils.UriInfos;
/**
* author Pascal Knueppel
* created at: 21.08.2022 - 09:24
*
* this class is used to resolve the bulkIds of different resources of a bulk request and to detect circular
* references
*/
public class BulkIdResolver
{
/**
* this map is used to map the ids of newly created resources to bulkIds
*/
private final Map resolvedBulkIds = new HashMap<>();
/**
* contains the bulkId reference details of the different request operations
*
* key: bulkId of the request operation
* value: details of the resolved operations
*/
private final Map bulkIdResourceResolverMap = new HashMap<>();
/**
* used to check for circular references within the request operation
*/
private final CircularReferenceDetector circularReferenceDetector = new CircularReferenceDetector();
/**
* will replace the bulkId references of all registered operations that do match the given bulkId with the
* given value
*
* @param bulkId the bulkId references that should be resolved
* @param resourceId the value that will replace the bulkId references
*/
public void addResolvedBulkId(String bulkId, String resourceId)
{
if (StringUtils.isBlank(bulkId))
{
return;
}
resolvedBulkIds.put(bulkId, resourceId);
List completelyResolvedOperations = new ArrayList<>();
bulkIdResourceResolverMap.values().forEach(resolver -> {
resolver.replaceBulkIdNode(bulkId, resourceId);
if (!resolver.hasAnyBulkIdReferences())
{
completelyResolvedOperations.add(bulkId);
}
});
completelyResolvedOperations.forEach(bulkIdResourceResolverMap::remove);
}
/**
* gets an already created bulkId resolver for the given bulkId if present
*/
public Optional getBulkIdResolver(String bulkId)
{
return Optional.ofNullable(bulkIdResourceResolverMap.get(bulkId));
}
/**
* analyzes the given resource for bulkId references and stores them and will also resolve them immediately if
* already possible
*
* @param operationBulkId the bulkId of the bulk operation that represents the given resource
* @param operationUriInfo the uri information of the currently accessed resource-type
* @param resourceString the resource itself
*/
public BulkIdResolverAbstract createNewBulkIdResolver(String operationBulkId,
UriInfos operationUriInfo,
String resourceString)
{
// if the abstractBulkIdResolver is not null the resource was already analyzed, and therefore we do not need
// to check again for bulkIds and illegal references
final ScimObjectNode resource;
if (HttpMethod.PATCH.equals(operationUriInfo.getHttpMethod()))
{
resource = JsonHelper.readJsonDocument(resourceString, PatchOpRequest.class);
}
else
{
resource = JsonHelper.readJsonDocument(resourceString, ScimObjectNode.class);
}
BulkIdResolverAbstract bulkIdResolverAbstract;
if (resource instanceof PatchOpRequest)
{
bulkIdResolverAbstract = new BulkIdResolverPatch(operationBulkId, operationUriInfo, (PatchOpRequest)resource);
}
else
{
bulkIdResolverAbstract = new BulkIdResolverResource(operationBulkId, operationUriInfo, resource);
}
bulkIdResolverAbstract.findAllBulkIdReferences();
checkForSelfReference(bulkIdResolverAbstract);
checkForCircularReferences(bulkIdResolverAbstract);
bulkIdResourceResolverMap.put(operationBulkId, bulkIdResolverAbstract);
if (bulkIdResolverAbstract.hasAnyBulkIdReferences())
{
resolvedBulkIds.forEach(bulkIdResolverAbstract::replaceBulkIdNode);
}
return bulkIdResolverAbstract;
}
/**
* checks if the given resource contains a reference to itself
*
* @param operationBulkId the bulkId operation that represents the resource within the BulkIdResourceResolver
* @param bulkIdResourceResolver the BulkIdResourceResolver that contains the analyzed resource
*/
private void checkForSelfReference(BulkIdResolverAbstract bulkIdResourceResolver)
{
boolean hasSelfReference = bulkIdResourceResolver.hasSelfReference();
if (hasSelfReference)
{
String errorMessage = String.format("the bulkId '%s' is a self-reference. Self-references will not be resolved",
bulkIdResourceResolver.getOperationBulkId());
throw new BadRequestException(errorMessage, null, ScimType.RFC7644.INVALID_VALUE);
}
}
/**
* initiates the circular reference detection with the newly just analyzed bulkIdResourceResolver
*/
private void checkForCircularReferences(BulkIdResolverAbstract bulkIdResourceResolver)
{
circularReferenceDetector.checkForCircles(bulkIdResourceResolver);
}
/**
* @return if there are still some bulkId references that are yet to be resolved
*/
public boolean isOpenBulkIdReferences()
{
return bulkIdResourceResolverMap.values().stream().anyMatch(BulkIdResolverAbstract::hasAnyBulkIdReferences);
}
/**
* checks if the given bulkId was already resolved. If this happens two operations from the bulk-request share
* the same bulkId
*/
public boolean isDuplicateBulkId(String bulkId)
{
return resolvedBulkIds.containsKey(bulkId);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy