in scim-server/src/main/java/org/apache/directory/scim/server/rest/BulkResourceImpl.java [110:333]
public Response doBulk(BulkRequest request, UriInfo uriInfo) {
BulkResponse response;
int errorCount = 0;
Integer requestFailOnErrors = request.getFailOnErrors();
int maxErrorCount = requestFailOnErrors != null && requestFailOnErrors > 0 ? requestFailOnErrors : Integer.MAX_VALUE;
int errorCountIncrement = requestFailOnErrors == null || requestFailOnErrors > 0 ? 1 : 0;
List<BulkOperation> bulkOperations = request.getOperations();
Map<String, BulkOperation> bulkIdKeyToOperationResult = new HashMap<>();
List<IWishJavaHadTuples> allUnresolveds = new ArrayList<>();
Map<String, Set<String>> reverseDependenciesGraph = this.generateReverseDependenciesGraph(bulkOperations);
Map<String, Set<String>> transitiveReverseDependencies = generateTransitiveDependenciesGraph(reverseDependenciesGraph);
log.debug("Reverse dependencies: {}", reverseDependenciesGraph);
log.debug("Transitive reverse dependencies: {}", transitiveReverseDependencies);
// clean out unwanted data
for (BulkOperation operationRequest : bulkOperations) {
operationRequest.setResponse(null);
operationRequest.setStatus(null);
}
// get all known bulkIds, handle bad input
for (BulkOperation operationRequest : bulkOperations) {
String bulkId = operationRequest.getBulkId();
Method method = operationRequest.getMethod();
String bulkIdKey = bulkId != null ? "bulkId:" + bulkId : null;
boolean errorOccurred = false;
// duplicate bulkId
if (bulkIdKey != null) {
if (!bulkIdKeyToOperationResult.containsKey(bulkIdKey)) {
bulkIdKeyToOperationResult.put(bulkIdKey, operationRequest);
} else {
errorOccurred = true;
BulkOperation duplicateOperation = bulkIdKeyToOperationResult.get(bulkIdKey);
createAndSetErrorResponse(operationRequest, Status.CONFLICT, "Duplicate bulkId");
if (!(duplicateOperation.getResponse() instanceof ErrorResponse)) {
duplicateOperation.setData(null);
createAndSetErrorResponse(duplicateOperation, Status.CONFLICT, "Duplicate bulkId");
}
}
}
// bad/missing input for method
if (method != null && !(operationRequest.getResponse() instanceof ErrorResponse)) {
switch (method) {
case POST:
case PUT: {
if (operationRequest.getData() == null) {
errorOccurred = true;
createAndSetErrorResponse(operationRequest, Status.BAD_REQUEST, "data not provided");
}
}
break;
case DELETE: {
String path = operationRequest.getPath();
if (path == null) {
errorOccurred = true;
createAndSetErrorResponse(operationRequest, Status.BAD_REQUEST, "path not provided");
} else if (!PATH_PATTERN.matcher(path)
.matches()) {
errorOccurred = true;
createAndSetErrorResponse(operationRequest, Status.BAD_REQUEST, "path is not a valid path (e.g. \"/Groups/123abc\", \"/Users/123xyz\", ...)");
} else {
String endPoint = path.substring(0, path.lastIndexOf('/'));
Class<ScimResource> clazz = (Class<ScimResource>) schemaRegistry.getScimResourceClassFromEndpoint(endPoint);
if (clazz == null) {
errorOccurred = true;
createAndSetErrorResponse(operationRequest, Status.BAD_REQUEST, "path does not contain a recognized endpoint (e.g. \"/Groups/...\", \"/Users/...\", ...)");
}
}
}
break;
case PATCH: {
errorOccurred = true;
createAndSetErrorResponse(operationRequest, Status.NOT_IMPLEMENTED, "Method not implemented: PATCH");
}
break;
default: {
}
break;
}
} else if (method == null) {
errorOccurred = true;
operationRequest.setData(null);
createAndSetErrorResponse(operationRequest, Status.BAD_REQUEST, "no method provided (e.g. PUT, POST, ...");
}
if (errorOccurred) {
operationRequest.setData(null);
if (bulkIdKey != null) {
Set<String> reverseDependencies = transitiveReverseDependencies.getOrDefault(bulkIdKey, Collections.emptySet());
String detail = String.format(OPERATION_DEPENDS_ON_FAILED_OPERATION, bulkIdKey);
for (String dependentBulkIdKey : reverseDependencies) {
BulkOperation dependentOperation = bulkIdKeyToOperationResult.get(dependentBulkIdKey);
if (!(dependentOperation.getResponse() instanceof ErrorResponse)) {
dependentOperation.setData(null);
createAndSetErrorResponse(dependentOperation, Status.CONFLICT, detail);
}
}
}
}
}
boolean errorCountExceeded = false;
// do the operations
for (BulkOperation operationResult : bulkOperations) {
if (!errorCountExceeded && !(operationResult.getResponse() instanceof ErrorResponse)) {
try {
this.handleBulkOperationMethod(allUnresolveds, operationResult, bulkIdKeyToOperationResult, uriInfo);
} catch (ResourceException resourceException) {
log.error("Failed to do bulk operation", resourceException);
errorCount += errorCountIncrement;
errorCountExceeded = errorCount >= maxErrorCount;
String detail = resourceException.getLocalizedMessage();
createAndSetErrorResponse(operationResult, resourceException.getStatus(), detail);
if (operationResult.getBulkId() != null) {
String bulkIdKey = "bulkId:" + operationResult.getBulkId();
this.cleanup(bulkIdKey, transitiveReverseDependencies, bulkIdKeyToOperationResult);
operationResult.setData(null);
}
} catch (UnresolvableOperationException unresolvableOperationException) {
log.error("Could not resolve bulkId during Bulk Operation method handling", unresolvableOperationException);
errorCount += errorCountIncrement;
String detail = unresolvableOperationException.getLocalizedMessage();
createAndSetErrorResponse(operationResult, Status.CONFLICT, detail);
if (operationResult.getBulkId() != null) {
String bulkIdKey = "bulkId:" + operationResult.getBulkId();
this.cleanup(bulkIdKey, transitiveReverseDependencies, bulkIdKeyToOperationResult);
operationResult.setData(null);
}
}
} else if (errorCountExceeded) {
// continue processing bulk operations to cleanup any dependencies
createAndSetErrorResponse(operationResult, Status.CONFLICT, "failOnErrors count reached");
if (operationResult.getBulkId() != null) {
String bulkIdKey = "bulkId:" + operationResult.getBulkId();
this.cleanup(bulkIdKey, transitiveReverseDependencies, bulkIdKeyToOperationResult);
}
}
}
// Resolve unresolved bulkIds
for (IWishJavaHadTuples iwjht : allUnresolveds) {
BulkOperation bulkOperationResult = iwjht.bulkOperationResult;
String bulkIdKey = iwjht.bulkIdKey;
ScimResource scimResource = bulkOperationResult.getData();
try {
for (UnresolvedTopLevel unresolved : iwjht.unresolveds) {
log.debug("Final resolution pass for {}", unresolved);
unresolved.resolve(scimResource, bulkIdKeyToOperationResult);
}
String scimResourceId = scimResource.getId();
@SuppressWarnings("unchecked")
Class<ScimResource> scimResourceClass = (Class<ScimResource>) scimResource.getClass();
Repository<ScimResource> repository = repositoryRegistry.getRepository(scimResourceClass);
repository.update(scimResourceId, null, scimResource, Collections.emptySet(), Collections.emptySet());
} catch (UnresolvableOperationException unresolvableOperationException) {
log.error("Could not complete final resolution pass, unresolvable bulkId", unresolvableOperationException);
String detail = unresolvableOperationException.getLocalizedMessage();
bulkOperationResult.setData(null);
bulkOperationResult.setLocation(null);
createAndSetErrorResponse(bulkOperationResult, Status.CONFLICT, detail);
this.cleanup(bulkIdKey, transitiveReverseDependencies, bulkIdKeyToOperationResult);
} catch (UnableToUpdateResourceException unableToUpdateResourceException) {
log.error("Failed to update Scim Resource with resolved bulkIds", unableToUpdateResourceException);
String detail = unableToUpdateResourceException.getLocalizedMessage();
bulkOperationResult.setData(null);
bulkOperationResult.setLocation(null);
createAndSetErrorResponse(bulkOperationResult, unableToUpdateResourceException.getStatus(), detail);
this.cleanup(bulkIdKey, transitiveReverseDependencies, bulkIdKeyToOperationResult);
} catch (ResourceException e) {
log.error("Could not complete final resolution pass, unresolvable bulkId", e);
String detail = e.getLocalizedMessage();
bulkOperationResult.setData(null);
bulkOperationResult.setLocation(null);
createAndSetErrorResponse(bulkOperationResult, Status.NOT_FOUND, detail);
this.cleanup(bulkIdKey, transitiveReverseDependencies, bulkIdKeyToOperationResult);
}
}
Status status = errorCountExceeded ? Status.BAD_REQUEST : Status.OK;
response = new BulkResponse()
.setOperations(bulkOperations)
.setStatus(status);
return Response.status(status)
.entity(response)
.build();
}