public Response doBulk()

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();
  }