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

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

package ca.gc.aafc.dina.service;

import ca.gc.aafc.dina.security.DinaAuthenticatedUser;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.Builder;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.javers.core.Javers;
import org.javers.core.metamodel.object.CdoSnapshot;
import org.javers.repository.jql.QueryBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@ConditionalOnProperty(value = "dina.auditing.enabled", havingValue = "true")
public class AuditService {

  private final Javers javers;
  private final Optional user;
  private final JaversDataService javersDataService;

  public static final String ANONYMOUS = "anonymous";

  /**
   * Returns true if the given audit instance has a terminal snapshot associated with it. Terminal snapshots
   * represent delete operations.
   *
   * @param auditInstance The audit instance to check
   * @return true if the given audit instance has a terminal snapshot associated with it
   */
  public boolean hasTerminalSnapshot(@NonNull AuditInstance auditInstance) {
    return this.findAll(auditInstance, null, Integer.MAX_VALUE, 0)
      .stream()
      .anyMatch(CdoSnapshot::isTerminal);
  }

  /**
   * Returns a list of Audit snapshots filtered by a given instance and author.
   * Author and instance can be null for un-filtered results.
   *
   * @param instance - instance to filter may be null
   * @param author   - author to filter may be null
   * @param limit    - limit of results
   * @param skip     - amount of results to skip
   * @return list of Audit snapshots
   */
  public List findAll(AuditInstance instance, String author, int limit, int skip) {
    return AuditService.findAll(this.javers, instance, author, limit, skip);
  }

  /**
   * Get the total resource count by a given Author and Audit Instance. Author and
   * instance can be null for un-filtered counts.
   *
   * @param author   - author to filter
   * @param instance - instance to filter
   * @return the total resource count
   */
  public Long getResouceCount(String author, AuditInstance instance) {
    return AuditService.getResouceCount(this.javersDataService, author, instance);
  }

  /**
   * Removes the snapshots for a given instance.
   *
   * @param instance - instance to remove
   */
  @Transactional
  public void removeSnapshots(@NonNull AuditInstance instance) {
    List snapshots = javers.findSnapshots(
      QueryBuilder.byInstanceId(instance.getId(), instance.getType()).build());
    javersDataService.removeSnapshots(snapshots.stream()
      .map(c -> c.getCommitId().valueAsNumber())
      .collect(Collectors.toList()), instance.getId(), instance.getType());
  }

  /**
   * Commit a snapshot for a given object, A dina authenticated user will be set
   * as the commit author. If a user is not present the author will be anonymous.
   *
   * @param obj - domain object state to persist
   */
  public void audit(@NonNull Object obj) {
    if (user.isPresent()) {
      this.javers.commit(user.get().getUsername(), obj);
    } else {
      this.javers.commit(ANONYMOUS, obj);
    }
  }

  /**
   * Commit a shallow delete snapshot for a given object, A dina authenticated
   * user will be set as the commit author. If a user is not present the author
   * will be anonymous.
   *
   * @param obj - domain object state to persist
   */
  public void auditDeleteEvent(@NonNull Object obj) {
    if (user.isPresent()) {
      this.javers.commitShallowDelete(user.get().getUsername(), obj);
    } else {
      this.javers.commitShallowDelete(ANONYMOUS, obj);
    }
  }

  /**
   * Returns a list of Audit snapshots using a given Javers Facade filtered by a
   * given instance and author. Author and instance can be null for un-filtered
   * results.
   *
   * @param javers   - Facade to query
   * @param instance - instance to filter may be null
   * @param author   - author to filter may be null
   * @param limit    - limit of results
   * @param skip     - amount of results to skip
   * @return list of Audit snapshots
   */
  public static List findAll(Javers javers, AuditInstance instance, String author, int limit, int skip) {
    QueryBuilder queryBuilder;

    if (instance != null) {
      queryBuilder = QueryBuilder.byInstanceId(instance.getId(), instance.getType());
    } else {
      queryBuilder = QueryBuilder.anyDomainObject();
    }

    if (StringUtils.isNotBlank(author)) {
      queryBuilder.byAuthor(author);
    }

    queryBuilder.limit(limit);
    queryBuilder.skip(skip);

    return javers.findSnapshots(queryBuilder.build());
  }

  /**
   * Get the total resource count by a given Author and/or Audit Instance. Author
   * and instance can be null for un-filtered counts.
   *
   * @param dataService     - JaversDataService to execute query
   * @param author   - author to filter
   * @param instance - instance filter to apply
   * @return the total resource count
   */
  public static Long getResouceCount(
    @NonNull JaversDataService dataService,
    String author,
    AuditInstance instance
  ) {
    String id = null;
    String type = null;

    if (instance != null) {
      id = instance.getId();
      type = instance.getType();
    }

    return dataService.getResourceCount(id, type, author);
  }

  @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE")
  @Builder
  @Data
  public static final class AuditInstance {

    @NonNull
    private final String type;
    @NonNull
    private final String id;

    /**
     * Returns an Optional AuditInstance from a string representation or empty if the
     * string is blank. Expected string format is {type}/{id}.
     *
     * @param instanceString - string to parse
     * @throws IllegalArgumentException if the string has an invalid format.
     * @return Optional AuditInstance or empty for blank strings
     */
    public static Optional fromString(String instanceString) {
      if (StringUtils.isBlank(instanceString)) {
        return Optional.empty();
      }

      String[] split = instanceString.split("/");
      if (split.length != 2) {
        throw new IllegalArgumentException(
          "Invalid ID must be formatted as {type}/{id}: " + instanceString);
      }
      return Optional.of(AuditInstance.builder().type(split[0]).id(split[1]).build());
    }

  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy