/**
   * Persists an SorPerson on update.
   *
   * @param sorPerson the person to update.
   * @return serviceExecutionResult.
   */
  public ServiceExecutionResult<SorPerson> updateSorPerson(final SorPerson sorPerson) {
    final Set validationErrors = this.validator.validate(sorPerson);

    if (!validationErrors.isEmpty()) {
      Iterator iter = validationErrors.iterator();
      while (iter.hasNext()) {
        logger.info("validation errors: " + iter.next());
      }
      // since because of existing design we cannot raise exception, we can only rollback the
      // transaction through code
      // OR-384
      if (TransactionAspectSupport.currentTransactionStatus() != null) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
      }

      return new GeneralServiceExecutionResult<SorPerson>(validationErrors);
    }

    // do reconciliationCheck to make sure that modifications do not cause person to reconcile to a
    // different person
    if (!this.reconciler.reconcilesToSamePerson(sorPerson)) {
      throw new IllegalStateException();
    }

    // Iterate over any sorRoles setting sorid and source id if not specified by SoR.
    for (final SorRole sorRole : sorPerson.getRoles()) {
      setRoleIdAndSource(sorRole, sorPerson.getSourceSor());
    }

    // Save Sor Person
    final SorPerson savedSorPerson = this.personRepository.saveSorPerson(sorPerson);

    Person person = this.findPersonById(savedSorPerson.getPersonId());

    Assert.notNull(person, "person cannot be null.");

    logger.info(
        "Verifying Number of calculated Roles before the calculate: " + person.getRoles().size());

    // Iterate over sorRoles. SorRoles may be new or updated.
    for (final SorRole savedSorRole : savedSorPerson.getRoles()) {
      logger.info(
          "DefaultPersonService: savedSorPersonRole Found, savedSorRoleID: "
              + savedSorRole.getId());
      logger.info("DefaultPersonService: savedSorPersonRole Found, Role Must be newly added.");
      // let sor role elector decide if this new role can be converted to calculated one
      sorRoleElector.addSorRole(savedSorRole, person);
      logger.info(
          "Verifying Number of calculated Roles after calculate: " + person.getRoles().size());
    }

    for (final IdentifierAssigner ia : this.identifierAssigners) {
      ia.addIdentifierTo(sorPerson, person);
    }

    person = recalculatePersonBiodemInfo(person, savedSorPerson, RecalculationType.UPDATE, false);
    person = this.personRepository.savePerson(person);

    return new GeneralServiceExecutionResult<SorPerson>(savedSorPerson);
  }
  @PreAuthorize("hasPermission(#sorRole, 'admin')")
  public ServiceExecutionResult<SorRole> validateAndSaveRoleForSorPerson(
      final SorPerson sorPerson, final SorRole sorRole) {
    logger.info(" validateAndSaveRoleForSorPerson start");
    Assert.notNull(sorPerson, "SorPerson cannot be null.");
    Assert.notNull(sorRole, "SorRole cannot be null.");

    // check if the SoR Role has an ID assigned to it already and assign source sor
    setRoleIdAndSource(sorRole, sorPerson.getSourceSor());

    final Set validationErrors = this.validator.validate(sorRole);

    if (!validationErrors.isEmpty()) {
      // since because of existing design we cannot raise exception, we can only rollback the
      // transaction through code
      // OR-384
      if (TransactionAspectSupport.currentTransactionStatus() != null) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
      }

      return new GeneralServiceExecutionResult<SorRole>(validationErrors);
    }

    final SorPerson newSorPerson = this.personRepository.saveSorPerson(sorPerson);
    Person person = this.personRepository.findByInternalId(newSorPerson.getPersonId());
    final SorRole newSorRole = newSorPerson.findSorRoleBySorRoleId(sorRole.getSorId());
    // let sor role elector decide if this new role can be converted to calculated one
    sorRoleElector.addSorRole(newSorRole, person);
    person = recalculatePersonBiodemInfo(person, newSorPerson, RecalculationType.UPDATE, false);
    this.personRepository.savePerson(person);
    logger.info("validateAndSaveRoleForSorPerson end");
    return new GeneralServiceExecutionResult<SorRole>(newSorRole);
  }
  protected Person addSorPersonAndLink(
      final ReconciliationCriteria reconciliationCriteria, final Person person)
      throws SorPersonAlreadyExistsException {
    final SorPerson sorPerson = reconciliationCriteria.getSorPerson();
    final SorPerson registrySorPerson =
        this.findByPersonIdAndSorIdentifier(person.getId(), sorPerson.getSourceSor());

    if (registrySorPerson != null) {
      // throw new IllegalStateException("Oops! An error occurred. A person already exists from this
      // SoR linked to this ID!");
      throw new SorPersonAlreadyExistsException(registrySorPerson);
    }

    if (!StringUtils.hasText(sorPerson.getSorId())) {
      sorPerson.setSorId(this.identifierGenerator.generateNextString());
    }

    // Iterate over any sorRoles setting sorid and source id
    for (final SorRole sorRole : sorPerson.getRoles()) {
      setRoleIdAndSource(sorRole, sorPerson.getSourceSor());
    }

    sorPerson.setPersonId(person.getId());
    final SorPerson savedSorPerson = this.personRepository.saveSorPerson(sorPerson);

    // Create calculated roles.
    for (final SorRole newSorRole : savedSorPerson.getRoles()) {
      // let sor role elector decide if this new role can be converted to calculated one
      sorRoleElector.addSorRole(newSorRole, person);
    }

    // loop through all identifiers to see if addition of this new sor person will have a new
    // identifier created at calculated level
    for (final IdentifierAssigner ia : this.identifierAssigners) {
      ia.addIdentifierTo(sorPerson, person);
    }
    Person p = recalculatePersonBiodemInfo(person, savedSorPerson, RecalculationType.UPDATE, false);
    return this.personRepository.savePerson(p);
  }
 @Override
 @Transactional(readOnly = true)
 public SorPerson findByIdentifierAndSource(
     String identifierType, String identifierValue, String sorSource) {
   Person p = this.findPersonByIdentifier(identifierType, identifierValue);
   if (p == null) {
     return null;
   }
   List<SorPerson> sorPeople = this.getSorPersonsFor(p);
   for (SorPerson sorPerson : sorPeople) {
     if (sorSource.equals(sorPerson.getSourceSor())) {
       return sorPerson;
     }
   }
   return null;
 }
  public ServiceExecutionResult<Person> validateAndSavePersonAndRole(
      final ReconciliationCriteria reconciliationCriteria) {
    logger.info(" validateAndSavePersonAndRole start");
    SorPerson sorPerson = reconciliationCriteria.getSorPerson();
    if (sorPerson == null || sorPerson.getRoles() == null)
      throw new IllegalArgumentException("Sor Person not found in provided criteria.");
    SorRole sorRole = reconciliationCriteria.getSorPerson().getRoles().get(0);
    if (sorRole == null)
      throw new IllegalArgumentException("Sor Role not found for provided criteria.");

    setRoleIdAndSource(sorRole, sorPerson.getSourceSor());

    final Set validationErrors = this.validator.validate(sorRole);

    if (!validationErrors.isEmpty()) {
      // since because of existing design we cannot raise exception, we can only rollback the
      // transaction through code
      // OR-384
      if (TransactionAspectSupport.currentTransactionStatus() != null) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
      }

      return new GeneralServiceExecutionResult<Person>(validationErrors);
    }

    Long personId = sorPerson.getPersonId();
    if (personId == null) {
      logger.info("calling saveSorPersonAndConvertToCalculatedPerson");
      // add new Sor Person and roles, create calculated person
      return new GeneralServiceExecutionResult<Person>(
          saveSorPersonAndConvertToCalculatedPerson(reconciliationCriteria));
    } else { // Add new Sor Person and role and link to the existing person
      Person thisPerson = this.personRepository.findByInternalId(personId);
      try {
        logger.info("calling addSorPersonAndLink");
        Person savedPerson = this.addSorPersonAndLink(reconciliationCriteria, thisPerson);
        return new GeneralServiceExecutionResult<Person>(savedPerson);
      } catch (SorPersonAlreadyExistsException sorE) {
        throw new IllegalArgumentException(
            "If a sor Person of the same source already exists, should call the other method to add the role only");
      }
    }
  }