All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.captaingoldfish.scim.sdk.server.endpoints.validation.ValidationContext Maven / Gradle / Ivy

There is a newer version: 1.26.0
Show newest version
// Generated by delombok at Thu Nov 02 20:38:53 CET 2023
package de.captaingoldfish.scim.sdk.server.endpoints.validation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import de.captaingoldfish.scim.sdk.common.constants.AttributeNames;
import de.captaingoldfish.scim.sdk.common.constants.HttpStatus;
import de.captaingoldfish.scim.sdk.common.exceptions.InternalServerException;
import de.captaingoldfish.scim.sdk.common.exceptions.ScimException;
import de.captaingoldfish.scim.sdk.common.response.ErrorResponse;
import de.captaingoldfish.scim.sdk.common.schemas.SchemaAttribute;
import de.captaingoldfish.scim.sdk.server.schemas.ResourceType;
import de.captaingoldfish.scim.sdk.server.schemas.exceptions.AttributeValidationException;


/**
 * @author Pascal Knueppel
 * @since 07.04.2021
 */
public class ValidationContext
{

  @java.lang.SuppressWarnings("all")
  private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ValidationContext.class);

  /**
   * contains errors that are not bound to any specific fields
   */
  private final List errors;

  /**
   * contains all error messages that are bound to a specific field
   */
  private final Map> fieldErrors;

  /**
   * the endpoint definition of the resource to validate
   */
  private final ResourceType resourceType;

  /**
   * additional headers that may be returned in case of validation error
   */
  private final Map responseHttpHeaders;

  /**
   * the response status that should be returned to the client. Is bad request (400) by default
   */
  private int httpResponseStatus;

  public ValidationContext(ResourceType resourceType)
  {
    this.errors = new ArrayList<>();
    this.fieldErrors = new HashMap<>();
    this.resourceType = resourceType;
    this.httpResponseStatus = HttpStatus.BAD_REQUEST;
    this.responseHttpHeaders = new HashMap<>();
  }

  /**
   * @return true if any errors have been set, false else
   */
  public boolean hasErrors()
  {
    boolean hasError;
    hasError = !errors.isEmpty() || !fieldErrors.isEmpty();
    return hasError;
  }

  /**
   * logs all reported errors on debug level
   */
  public void logErrors()
  {
    errors.forEach(log::debug);
    fieldErrors.forEach((fieldName, errorList) -> {
      errorList.forEach(errorMessage -> log.debug("{}: {}", fieldName, errorMessage));
    });
  }

  /**
   * adds an error that is not bound to a specific resource field
   *
   * @param errorMessage the error message
   */
  public void addError(String errorMessage)
  {
    if (StringUtils.isNotBlank(errorMessage))
    {
      errors.add(errorMessage);
    }
    else
    {
      log.trace("Not adding empty messages to error context.");
    }
  }

  /**
   * adds an error that is bound to a specific resource field
   *
   * @param fieldName the name of the field to which the error is bound
   * @param errorMessage the error message
   */
  public void addError(String fieldName, String errorMessage)
  {
    Optional schemaAttributeList = resourceType.getAllSchemas()
                                                                .stream()
                                                                .map(schema -> schema.getSchemaAttribute(fieldName))
                                                                .filter(Objects::nonNull)
                                                                .findAny();
    boolean attributeNotFound = !schemaAttributeList.isPresent();
    if (attributeNotFound)
    {
      String error = String.format("Cannot bind field with name \'%s\' on error constraint because no such field "
                                   + "exists for resource \'%s\'",
                                   fieldName,
                                   resourceType.getMainSchema().getNonNullId());
      throw new InternalServerException(error);
    }
    List fieldErrorList = fieldErrors.computeIfAbsent(fieldName, getOrCreateList -> new ArrayList<>());
    fieldErrorList.add(errorMessage);
  }

  /**
   * adds specific field errors to the validation context
   *
   * @param ex the definition of the error that occurred
   */
  public void addExceptionMessages(AttributeValidationException ex)
  {
    final String fieldName = ex.getSchemaAttribute().getScimNodeName();
    Throwable cause = ex;
    while (cause != null)
    {
      addError(fieldName, cause.getMessage());
      cause = cause.getCause();
    }
    log.debug(ex.getMessage(), ex);
  }

  /**
   * adds other more unspecific error messages to the context that are not directly related to any fields
   *
   * @param ex the definition of the error that occurred
   */
  public void addExceptionMessages(ScimException ex)
  {
    Throwable cause = ex;
    while (cause != null)
    {
      addError(cause.getMessage());
      cause = cause.getCause();
    }
  }

  /**
   * adds the current errors of this validation context to the given error response
   */
  public void writeToErrorResponse(ErrorResponse errorResponse)
  {
    Optional errorMessagesArray = addUnspecificErrorMessages();
    Optional fieldErrorsObject = addFieldSpecificErrorMessages();
    if (errorMessagesArray.isPresent())
    {
      errorResponse.setDetail(errorMessagesArray.get().get(0).textValue());
    }
    else
    {
      String firstErrorMessage = fieldErrors.get(fieldErrors.keySet().iterator().next()).get(0);
      errorResponse.setDetail(firstErrorMessage);
    }
    ObjectNode errorNode = new ObjectNode(JsonNodeFactory.instance);
    errorMessagesArray.ifPresent(array -> errorNode.set(AttributeNames.Custom.ERROR_MESSAGES, array));
    fieldErrorsObject.ifPresent(object -> errorNode.set(AttributeNames.Custom.FIELD_ERRORS, object));
    errorResponse.set(AttributeNames.Custom.ERRORS, errorNode);
    errorResponse.setStatus(httpResponseStatus);
    responseHttpHeaders.forEach((headerKey, headerValue) -> {
      errorResponse.getHttpHeaders().put(headerKey, headerValue);
    });
  }

  /**
   * if unspecific errors are present an array node will be created with the error messages
   */
  private Optional addUnspecificErrorMessages()
  {
    if (errors.isEmpty())
    {
      return Optional.empty();
    }
    ArrayNode errorMessages = new ArrayNode(JsonNodeFactory.instance);
    errors.forEach(errorMessages::add);
    return Optional.of(errorMessages);
  }

  /**
   * if field errors are present an object node will be created and the field errors will be added into the
   * specific object node
   *
   * @return an empty if no field errors are present or an object node that represents the field errors
   */
  private Optional addFieldSpecificErrorMessages()
  {
    if (fieldErrors.isEmpty())
    {
      return Optional.empty();
    }
    ObjectNode fieldErrorNode = new ObjectNode(JsonNodeFactory.instance);
    fieldErrors.forEach((fieldName, errorMessageList) -> {
      ArrayNode errorMessages = new ArrayNode(JsonNodeFactory.instance);
      errorMessageList.forEach(errorMessages::add);
      fieldErrorNode.set(fieldName, errorMessages);
    });
    return Optional.of(fieldErrorNode);
  }

  /**
   * contains errors that are not bound to any specific fields
   */
  @java.lang.SuppressWarnings("all")
  public List getErrors()
  {
    return this.errors;
  }

  /**
   * contains all error messages that are bound to a specific field
   */
  @java.lang.SuppressWarnings("all")
  public Map> getFieldErrors()
  {
    return this.fieldErrors;
  }

  /**
   * the endpoint definition of the resource to validate
   */
  @java.lang.SuppressWarnings("all")
  public ResourceType getResourceType()
  {
    return this.resourceType;
  }

  /**
   * additional headers that may be returned in case of validation error
   */
  @java.lang.SuppressWarnings("all")
  public Map getResponseHttpHeaders()
  {
    return this.responseHttpHeaders;
  }

  /**
   * the response status that should be returned to the client. Is bad request (400) by default
   */
  @java.lang.SuppressWarnings("all")
  public int getHttpResponseStatus()
  {
    return this.httpResponseStatus;
  }

  /**
   * the response status that should be returned to the client. Is bad request (400) by default
   */
  @java.lang.SuppressWarnings("all")
  public void setHttpResponseStatus(final int httpResponseStatus)
  {
    this.httpResponseStatus = httpResponseStatus;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy