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

ca.gc.aafc.dina.service.DefaultDinaService Maven / Gradle / Ivy

The newest version!
package ca.gc.aafc.dina.service;

import ca.gc.aafc.dina.entity.DinaEntity;
import ca.gc.aafc.dina.jpa.BaseDAO;
import ca.gc.aafc.dina.jpa.PredicateSupplier;
import ca.gc.aafc.dina.validation.ValidationErrorsHelper;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ObjectError;
import org.springframework.validation.SmartValidator;
import org.springframework.validation.Validator;

import javax.inject.Inject;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import javax.validation.groups.Default;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;

/**
 * Service class for database interactions with a {@link DinaEntity}.
 *
 * @param  - Type of {@link DinaEntity}
 */
@Component
@RequiredArgsConstructor(onConstructor_ = @Inject)
public class DefaultDinaService implements DinaService {

  @NonNull
  private final BaseDAO baseDAO;

  @NonNull
  private final SmartValidator validator;

  /**
   * Persist an instance of the provided entity in the database.
   *
   * @param entity entity to persist
   * @return returns the original entity.
   */
  @Override
  public E create(E entity) {
    return create(entity, false);
  }

  /**
   * Persist an instance of the provided entity and flush the content of the current transaction
   * to the database.
   *
   * @param entity entity to persist
   * @return returns the original entity.
   */
  public E createAndFlush(E entity) {
    return create(entity, true);
  }

  /**
   * Private method handling entity create
   * @param entity
   * @param flush
   * @return
   */
  private E create(E entity, boolean flush) {
    preCreate(entity);
    validateConstraints(entity, OnCreate.class);
    validateBusinessRules(entity);
    baseDAO.create(entity, flush);
    postCreate(entity);
    return entity;
  }

  /**
   * Merge the state of a given entity into the current persistence context.
   *
   * @param entity entity to update
   * @return returns the managed instance the state was merged to.
   */
  @Override
  public E update(E entity) {
    preUpdate(entity);
    validateConstraints(entity, OnUpdate.class);
    validateBusinessRules(entity);
    return baseDAO.update(entity);
  }

  /**
   * Remove the given entity from the database.
   *
   * @param entity entity to delete
   */
  @Override
  public void delete(E entity) {
    preDelete(entity);
    baseDAO.delete(entity);
  }

  /**
   * Returns a list of Entities of a given class restricted by the predicates returned by a given function.
   *
   * @param entityClass - entity class to query cannot be null
   * @param where       - function to return the predicates cannot be null
   * @param orderBy     - function to return the sorting criteria can be null
   * @param startIndex  - position of first result to retrieve
   * @param maxResult   - maximum number of results to return
   * @return list of entities
   */
  @Override
  public  List findAll(
    @NonNull Class entityClass,
    @NonNull BiFunction, Predicate[]> where,
    BiFunction, List> orderBy,
    int startIndex,
    int maxResult
  ) {
    return findAll(entityClass, (criteriaBuilder, root, em) -> where.apply(criteriaBuilder, root),
      orderBy, startIndex, maxResult, Set.of(), Set.of());
  }

  /**
   * See {@link BaseDAO#refresh(Object)}
   */
  public void refresh(Object entity) {
    baseDAO.refresh(entity);
  }

  /**
   * See {@link BaseDAO#detach(Object)}
   */
  public void detach(Object entity) {
    baseDAO.detach(entity);
  }

  /**
   * See {@link BaseDAO#flush()}
   */
  public void flush() {
    baseDAO.flush();
  }


  @Override
  public  List findAll(
    @NonNull Class entityClass,
    @NonNull PredicateSupplier where,
    BiFunction, List> orderBy,
    int startIndex,
    int maxResult,
    @NonNull Set includes,
    @NonNull Set relationships
  ) {
    CriteriaBuilder criteriaBuilder = baseDAO.getCriteriaBuilder();
    CriteriaQuery criteria = criteriaBuilder.createQuery(entityClass);
    Root root = criteria.from(entityClass);
    Predicate[] predicates = baseDAO.buildPredicateFromSupplier(where, criteriaBuilder, root);
    criteria.where(predicates).select(root);
    if (orderBy != null) {
      criteria.orderBy(orderBy.apply(criteriaBuilder, root));
    }

    Map hints = relationships.isEmpty() ? null : relationshipPathToLoadHints(entityClass, relationships);
    return baseDAO.resultListFromCriteria(criteria, startIndex, maxResult, hints);
  }

  /**
   * Returns the resource count from a given predicate supplier.
   *
   * @param entityClass       - entity class to query cannot be null
   * @param predicateSupplier - function to return the predicates cannot be null
   * @return resource count
   */
  @Override
  public  Long getResourceCount(
    @NonNull Class entityClass,
    @NonNull PredicateSupplier predicateSupplier
  ) {
    return baseDAO.getResourceCount(entityClass, predicateSupplier);
  }

  /**
   * Returns the resource count from a given predicate supplier.
   *
   * @param entityClass       - entity class to query cannot be null
   * @param predicateSupplier - function to return the predicates cannot be null
   * @return resource count
   */
  @Override
  public  Long getResourceCount(
    @NonNull Class entityClass,
    @NonNull BiFunction, Predicate[]> predicateSupplier
  ) {
    return getResourceCount(
      entityClass,
      (criteriaBuilder, root, em) -> predicateSupplier.apply(criteriaBuilder, root));
  }


  @Override
  public  T findOne(Object naturalId, Class entityClass) {
    return baseDAO.findOneByNaturalId(naturalId, entityClass);
  }

  @Override
  public  T findOne(Object naturalId, Class entityClass, Set relationships) {
    Map hints = relationships.isEmpty() ? null : relationshipPathToLoadHints(entityClass, relationships);
    return baseDAO.findOneByNaturalId(naturalId, entityClass, hints);
  }

  /**
   * Find an entity by its database id.
   *
   * @param id   - id of entity
   * @param entityClass - class of entity
   * @return the matched entity
   */
  public  T findOneById(Object id, Class entityClass) {
    return baseDAO.findOneByDatabaseId(id, entityClass);
  }

  /**
   * Find an entity by a specific property.
   *
   * @param clazz
   * @param property
   * @param value
   * @return the entity or null if not found
   */
  public E findOneByProperty(Class clazz, String property, Object value) {
    return baseDAO.findOneByProperty(clazz, property, value);
  }

  /**
   * Find an entity by a specific properties.
   * The combination of the properties are assumed to be unique and returned only 1 entity.
   *
   * @param clazz
   * @param propertiesAndValue
   * @return the entity or null if not found
   */
  public E findOneByProperties(Class clazz,List> propertiesAndValue) {
    return baseDAO.findOneByProperties(clazz, propertiesAndValue);
  }

  /**
   * Returns a reference to an entity that should exist without actually loading it. Useful to set
   * relationships without loading the entity instead of findOne.
   *
   * @param naturalId   - natural id of entity
   * @param entityClass - class of entity
   * @return the matched reference
   */
  @Override
  public  T getReferenceByNaturalId(Class entityClass, Object naturalId) {
    return baseDAO.getReferenceByNaturalId(entityClass, naturalId);
  }

  /**
   * Check for the existence of a record by natural id.
   */
  @Override
  public boolean exists(Class entityClass, Object naturalId) {
    return baseDAO.existsByNaturalId(naturalId, entityClass);
  }

  /**
   * Run before the {@link DefaultDinaService#create(DinaEntity)} method.
   *
   * @param entity entity being created by {@link DefaultDinaService#create(DinaEntity)}
   */
  protected void preCreate(E entity) {
    // Defaults to do nothing
  }

  /**
   * Run after the {@link DefaultDinaService#create(DinaEntity)} method.
   *
   * @param entity entity created by {@link DefaultDinaService#create(DinaEntity)}
   */
  protected void postCreate(E entity) {
    // Defaults to do nothing
  }

  /**
   * Run before the {@link DefaultDinaService#update(DinaEntity)} method.
   *
   * @param entity entity being updated by {@link DefaultDinaService#update(DinaEntity)}
   */
  protected void preUpdate(E entity) {
    // Defaults to do nothing
  }

  /**
   * Run before the {@link DefaultDinaService#delete(DinaEntity)} method.
   *
   * @param entity entity being deleted by {@link DefaultDinaService#delete(DinaEntity)}
   */
  protected void preDelete(E entity) {
    // Defaults to do nothing
  }

  /**
   * Check for the existence of a record based on a property and a value
   * 
   * @param clazz
   * @param property
   * @param value
   * @return
   */
  public boolean existsByProperty(Class clazz, String property, Object value) {
    return baseDAO.existsByProperty(clazz, property, value);
  }

  /**
   * Find a list of entities by a specific property.
   * 
   * @param clazz
   * @param property
   * @param value
   * @return the entity or null if not found
   */
  public List findByProperty(Class clazz, String property, Object value) {
    return baseDAO.findByProperty(clazz, property, value);
  }

  /**
   * Function that validates an entity against a specific validator to check business rules.
   * @param entity
   * @param validator business rules validator
   * @throws ValidationException if the validator returned an error
   */
  protected void applyBusinessRule(E entity, Validator validator) {
    Objects.requireNonNull(entity);
    applyBusinessRule(entity, validator, ValidationErrorsHelper.newErrorsObject(entity));
  }

  /**
   * Function that validates an object against a specific validator to check business rules.
   * This function should be used to validate objects that are not {@link DinaEntity}.
   * @param objIdentifier
   * @param target
   * @param validator
   */
  protected static void applyBusinessRule(String objIdentifier, Object target, Validator validator) {
    Objects.requireNonNull(target);
    applyBusinessRule(target, validator, ValidationErrorsHelper.newErrorsObject(objIdentifier, target));
  }

  private static void applyBusinessRule(Object target, Validator validator, Errors errors) {
    Objects.requireNonNull(target);
    Objects.requireNonNull(errors);

    validator.validate(target, errors);
    ValidationErrorsHelper.errorsToValidationException(errors);
  }

  @Override
  public void validateBusinessRules(E entity) {
  }

  private  Map relationshipPathToLoadHints(Class clazz, Set rel) {
    if (rel.isEmpty() ) {
      return Map.of();
    }
    return Map.of(BaseDAO.LOAD_GRAPH_HINT_KEY, baseDAO.createEntityGraph(clazz, rel.toArray(new String[0])));
  }

  @SuppressWarnings("unchecked")
  @Override
  public void validateConstraints(E entity, Class validationGroup) {
    Errors errors = ValidationErrorsHelper.newErrorsObject(entity);

    validator.validate(entity, errors, validationGroup);

    if (errors.hasErrors()) {
      Set> violations = new HashSet<>();
      for (ObjectError o : errors.getAllErrors()) {
        if (o.contains(ConstraintViolation.class)) { 
          violations.add((ConstraintViolation) o.unwrap(ConstraintViolation.class));
        }
      }
      throw new ConstraintViolationException(violations);
    }
  }

  /**
   * Optional group standardization method.
   *
   * @param entity
   * @return standardized group name (lowercase)
   */
  public String standardizeGroupName(E entity) {
    if (entity.getGroup() == null) {
      return null;
    }
    return entity.getGroup().toLowerCase();
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy