org.openregistry.core.service.DefaultPersonService.java Source code

Java tutorial

Introduction

Here is the source code for org.openregistry.core.service.DefaultPersonService.java

Source

/**
 * Licensed to Jasig under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Jasig licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a
 * copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.openregistry.core.service;

import org.apache.commons.lang.*;
import org.openregistry.core.domain.*;
import org.openregistry.core.domain.DisclosureSettings.PropertyNames;
import org.openregistry.core.domain.sor.*;
import org.openregistry.core.repository.*;
import org.openregistry.core.service.identifier.*;
import org.openregistry.core.service.identitycard.IdCardGenerator;
import org.openregistry.core.service.reconciliation.*;
import org.slf4j.*;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.transaction.annotation.*;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.util.*;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.inject.*;
import javax.validation.*;
import java.util.*;

/**
 * Default implementation of the {@link PersonService}.
 *
 * @author Scott Battaglia
 * @author Dmitriy Kopylenko
 * @since 1.0.0
 */
@Named("personService")
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public class DefaultPersonService implements PersonService {

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    private final PersonRepository personRepository;

    private final ReferenceRepository referenceRepository;

    private final DisclosureRecalculationStrategyRepository strategyRepository;

    private final Reconciler reconciler;

    private final IdentifierGenerator identifierGenerator;

    private Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

    @Resource(name = "sorRoleElector")
    private SorRoleElector sorRoleElector = new DefaultSorRoleElector();

    @Resource(name = "birthDateFieldElector")
    private FieldElector<Date> birthDateFieldElector = new DefaultBirthDateFieldElector();

    @Resource(name = "genderFieldElector")
    private FieldElector<String> genderFieldElector = new DefaultGenderFieldElector();

    @Resource(name = "preferredNameFieldElector")
    private FieldElector<SorName> preferredNameFieldElector = new DefaultNameFieldSelector();

    @Resource(name = "officialNameFieldElector")
    private FieldElector<SorName> officialNameFieldElector = new DefaultNameFieldSelector();

    @Resource(name = "personAttributesElector")
    private FieldElector<Map<String, String>> attributesElector = new DefaultAttributesElector();

    private FieldElector<EmailAddress> preferredContactEmailAddressFieldElector = new DefaultPreferredEmailContactFieldSelector();

    private FieldElector<Phone> preferredContactPhoneNumberFieldElector = new DefaultPreferredPhoneContactFieldSelector();

    @Resource(name = "disclosureFieldElector")
    private FieldElector<SorDisclosureSettings> disclosureFieldElector = new DefaultDisclosureSettingsFieldElector();

    @Resource(name = "ssnFieldElector")
    private FieldElector<String> ssnFieldElector = new DefaultSSNFieldElector();

    private enum RecalculationType {
        DELETE, ADD, UPDATE
    }

    @Resource(name = "personFactory")
    private ObjectFactory<Person> personObjectFactory;

    @Autowired(required = false)
    private Map<ReconciliationCriteria, ReconciliationResult> criteriaCache = new EhCacheBackedMapImpl<ReconciliationCriteria, ReconciliationResult>();

    @Autowired(required = false)
    private List<IdentifierAssigner> identifierAssigners = new ArrayList<IdentifierAssigner>();

    @Inject
    private IdentifierChangeService identifierChangeService;

    @Resource(name = "idCardGenerator")
    private IdCardGenerator idCardGenerator;

    @Inject
    public DefaultPersonService(final PersonRepository personRepository,
            final ReferenceRepository referenceRepository,
            final DisclosureRecalculationStrategyRepository strategyRepository,
            final IdentifierGenerator identifierGenerator, final Reconciler reconciler) {
        this.personRepository = personRepository;
        this.referenceRepository = referenceRepository;
        this.strategyRepository = strategyRepository;
        this.identifierGenerator = identifierGenerator;
        this.reconciler = reconciler;
    }

    public void setPreferredContactEmailAddressFieldElector(
            final FieldElector<EmailAddress> preferredContactEmailAddressFieldElector) {
        this.preferredContactEmailAddressFieldElector = preferredContactEmailAddressFieldElector;
    }

    public void setPreferredContactPhoneNumberFieldElector(
            final FieldElector<Phone> preferredContactPhoneNumberFieldElector) {
        this.preferredContactPhoneNumberFieldElector = preferredContactPhoneNumberFieldElector;
    }

    public void setPersonObjectFactory(final ObjectFactory<Person> personObjectFactory) {
        this.personObjectFactory = personObjectFactory;
    }

    public void setCriteriaCache(final Map<ReconciliationCriteria, ReconciliationResult> criteriaCache) {
        this.criteriaCache = criteriaCache;
    }

    public void setIdentifierAssigners(final List<IdentifierAssigner> identifierAssigners) {
        this.identifierAssigners = identifierAssigners;
    }

    public void setValidator(final Validator validator) {
        this.validator = validator;
    }

    @Transactional(readOnly = true)
    public Person findPersonById(final Long id) {
        return this.personRepository.findByInternalId(id);
    }

    @Transactional(readOnly = true)
    public Person fetchCompleteCalculatedPerson(Long id) {
        return this.personRepository.fetchCompleteCalculatedPerson(id);
    }

    @Transactional(readOnly = true)
    public Person findPersonByIdentifier(final String identifierType, final String identifierValue) {
        try {
            return this.personRepository.findByIdentifier(identifierType, identifierValue);
        } catch (final Exception e) {
            return null;
        }
    }

    @Transactional(readOnly = true)
    public SorPerson findByPersonIdAndSorIdentifier(final Long personId, final String sorSourceIdentifier) {
        try {
            return this.personRepository.findByPersonIdAndSorIdentifier(personId, sorSourceIdentifier);
        } catch (final Exception e) {
            //logger.debug(e.getMessage(), e);
            logger.debug(e.getMessage());
            return null;
        }
    }

    public SorPerson findBySorIdentifierAndSource(final String sorSource, final String sorId) {
        try {
            return this.personRepository.findBySorIdentifierAndSource(sorSource, sorId);
        } catch (Exception e) {
            return null;
        }
    }

    @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;
    }

    @Override
    @Transactional(readOnly = true)
    public List<SorPerson> findByIdentifier(String identifierType, String identifierValue) {
        //TODO-Verify if this is the correct behavior
        Person p = this.findPersonByIdentifier(identifierType, identifierValue);
        if (p == null) {
            return null;
        }
        List<SorPerson> sorPeople = this.getSorPersonsFor(p);
        return sorPeople;
    }

    @Override
    public List<SorPerson> getSorPersonsFor(final Person person) {
        Assert.notNull(person);
        return this.personRepository.getSoRRecordsForPerson(person);
    }

    @Override
    public List<SorPerson> getSorPersonsFor(final Long personId) {
        return getSorPersonsFor(this.personRepository.findByInternalId(personId));
    }

    /**
     * This does not explicitly delete the names because its assumed the recalculation will clean it up.
     */
    public boolean deleteSystemOfRecordPerson(final SorPerson sorPerson, final boolean mistake,
            final String terminationTypes) {
        Assert.notNull(sorPerson, "sorPerson cannot be null.");
        final String terminationTypeToUse = terminationTypes != null ? terminationTypes
                : Type.TerminationTypes.UNSPECIFIED.name();

        final Person person = this.personRepository.findByInternalId(sorPerson.getPersonId());
        Assert.notNull(person, "person cannot be null.");

        if (mistake) {
            Set<Role> rolesToDelete = new HashSet<Role>();

            for (final SorRole sorRole : sorPerson.getRoles()) {
                for (final Role role : person.getRoles()) {
                    if (sorRole.getId().equals(role.getSorRoleId())) {
                        rolesToDelete.add(role);
                    }
                }
            }

            for (final Role role : rolesToDelete) {
                //let sorRoleElector delete the role and add another role if required
                sorRoleElector.removeCalculatedRole(person, role,
                        this.personRepository.getSoRRecordsForPerson(person));
            }

            final Number number = this.personRepository.getCountOfSoRRecordsForPerson(person);

            if (number.intValue() == 1) {
                this.personRepository.deletePerson(person);
            }

            this.personRepository.deleteSorPerson(sorPerson);
            return true;
        }

        //we do this explicitly here because once they're gone we can't re-calculate?  We might move to this to the recalculateCalculatedPerson method.
        final Type terminationReason = this.referenceRepository.findType(Type.DataTypes.TERMINATION,
                terminationTypeToUse);

        for (final SorRole sorRole : sorPerson.getRoles()) {
            for (final Role role : person.getRoles()) {
                if (!role.isTerminated() && sorRole.getId().equals(role.getSorRoleId())) {
                    role.expireNow(terminationReason, true);
                }
            }
        }

        this.personRepository.deleteSorPerson(sorPerson);
        this.personRepository.savePerson(person);

        Person p = recalculatePersonBiodemInfo(person, sorPerson, RecalculationType.DELETE, mistake);
        this.personRepository.savePerson(p);
        return true;
    }

    public boolean deleteSystemOfRecordPerson(final String sorSource, final String sorId, final boolean mistake,
            final String terminationTypes) {
        Assert.notNull(sorSource, "sorSource cannot be null.");
        Assert.notNull(sorId, "sorId cannot be null.");
        final SorPerson sorPerson = this.personRepository.findBySorIdentifierAndSource(sorSource, sorId);

        return sorPerson != null && deleteSystemOfRecordPerson(sorPerson, mistake, terminationTypes);
    }

    public boolean deleteSystemOfRecordRole(final SorPerson sorPerson, final SorRole sorRole, final boolean mistake,
            final String terminationTypes) throws IllegalArgumentException {
        Assert.notNull(sorRole, "sorRole cannot be null.");
        Assert.notNull(sorPerson, "soPerson cannot be null.");
        final String terminationTypeToUse = terminationTypes != null ? terminationTypes
                : Type.TerminationTypes.UNSPECIFIED.name();

        final Person person = this.personRepository.findByInternalId(sorPerson.getPersonId());
        Assert.notNull(person, "person cannot be null.");

        final Role role = person.findRoleBySoRRoleId(sorRole.getId());
        //delete and expire role only if it exist at calculated level
        if (role != null)
            if (mistake) {

                //let SorRoleElector remove the role
                sorRoleElector.removeCalculatedRole(person, role,
                        this.personRepository.getSoRRecordsForPerson(person));

            } else {
                final Type terminationReason = this.referenceRepository.findType(Type.DataTypes.TERMINATION,
                        terminationTypeToUse);
                if (!role.isTerminated()) {
                    role.expireNow(terminationReason, true);
                }
            }

        sorPerson.getRoles().remove(sorRole);
        this.personRepository.saveSorPerson(sorPerson);
        this.personRepository.savePerson(person);
        return true;
    }

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

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

    }

    public ServiceExecutionResult<Person> addPerson(final ReconciliationCriteria reconciliationCriteria)
            throws ReconciliationException, IllegalArgumentException, SorPersonAlreadyExistsException {
        Assert.notNull(reconciliationCriteria, "reconciliationCriteria cannot be null");
        logger.info("addPerson start");
        if (reconciliationCriteria.getSorPerson().getSorId() != null
                && this.findBySorIdentifierAndSource(reconciliationCriteria.getSorPerson().getSourceSor(),
                        reconciliationCriteria.getSorPerson().getSorId()) != null) {
            //throw new IllegalStateException("CANNOT ADD SAME SOR RECORD.");
            throw new SorPersonAlreadyExistsException(
                    this.findBySorIdentifierAndSource(reconciliationCriteria.getSorPerson().getSourceSor(),
                            reconciliationCriteria.getSorPerson().getSorId()));
        }

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

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

        final ReconciliationResult result = this.reconciler.reconcile(reconciliationCriteria);

        switch (result.getReconciliationType()) {
        case NONE:
            return new GeneralServiceExecutionResult<Person>(
                    saveSorPersonAndConvertToCalculatedPerson(reconciliationCriteria));

        case EXACT:
            return new GeneralServiceExecutionResult<Person>(
                    addNewSorPersonAndLinkWithMatchedCalculatedPerson(reconciliationCriteria, result));
        }

        this.criteriaCache.put(reconciliationCriteria, result);
        logger.info("addPerson start");
        throw new ReconciliationException(result);
    }

    public ServiceExecutionResult<Person> forceAddPerson(final ReconciliationCriteria reconciliationCriteria)
            throws IllegalArgumentException, IllegalStateException {
        Assert.notNull(reconciliationCriteria, "reconciliationCriteria cannot be null.");
        logger.info("forceAddPerson start");
        final ReconciliationResult result = this.criteriaCache.get(reconciliationCriteria);

        if (result == null) {
            throw new IllegalStateException("No ReconciliationResult found for provided criteria.");
        }

        this.criteriaCache.remove(reconciliationCriteria);
        logger.info("forceAddPerson end");
        return new GeneralServiceExecutionResult<Person>(
                saveSorPersonAndConvertToCalculatedPerson(reconciliationCriteria));
    }

    public ServiceExecutionResult<Person> addPersonAndLink(final ReconciliationCriteria reconciliationCriteria,
            final Person person)
            throws IllegalArgumentException, IllegalStateException, SorPersonAlreadyExistsException {
        Assert.notNull(reconciliationCriteria, "reconciliationCriteria cannot be null.");
        Assert.notNull(person, "person cannot be null.");
        logger.info(" addPersonAndLink start");
        final ReconciliationResult result = this.criteriaCache.get(reconciliationCriteria);

        if (result == null) {
            throw new IllegalStateException("No ReconciliationResult found for provided criteria.");
        }

        for (final PersonMatch personMatch : result.getMatches()) {
            if (personMatch.getPerson().getId().equals(person.getId())) {
                addSorPersonAndLink(reconciliationCriteria, person);
                final Person savedPerson = this.personRepository.findByInternalId(person.getId());
                return new GeneralServiceExecutionResult<Person>(savedPerson);
            }
        }
        logger.info("addPersonAndLink end");
        throw new IllegalStateException("Person not found in ReconciliationResult.");
    }

    public ServiceExecutionResult<ReconciliationResult> reconcile(
            final ReconciliationCriteria reconciliationCriteria) throws IllegalArgumentException {
        Assert.notNull(reconciliationCriteria, "reconciliationCriteria cannot be null");
        logger.info("reconcile start");
        final Set validationErrors = this.validator.validate(reconciliationCriteria);

        if (!validationErrors.isEmpty()) {
            Iterator iter = validationErrors.iterator();
            while (iter.hasNext()) {
                logger.info("validation errors: " + iter.next());
            }
            logger.info("reconcile start");
            //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<ReconciliationResult>(validationErrors);
        }

        final ReconciliationResult result = this.reconciler.reconcile(reconciliationCriteria);
        //(reconciliationCriteria, result);
        return new GeneralServiceExecutionResult<ReconciliationResult>(result);
    }

    @PostFilter("hasPermission(filterObject, 'read')")
    public List<PersonMatch> searchForPersonBy(final SearchCriteria searchCriteria) {

        if (StringUtils.hasText(searchCriteria.getIdentifierValue())) {
            final String identifierValue = searchCriteria.getIdentifierValue();
            final Person person = this.findPersonByIdentifier(searchCriteria.getIdentifierType().getName(),
                    identifierValue);
            if (person != null)
                return new ArrayList<PersonMatch>(
                        Arrays.asList(new PersonMatchImpl(person, 100, new ArrayList<FieldMatch>())));
            else
                return new ArrayList<PersonMatch>();
        }
        final List<Person> persons = this.personRepository.searchByCriteria(searchCriteria);
        return createMatches(persons);
    }

    /**
     * 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);
    }

    public ServiceExecutionResult<SorRole> updateSorRole(final SorPerson sorPerson, final SorRole sorRole) {
        Assert.notNull(sorPerson, "sorPerson cannot be null.");
        Assert.notNull(sorRole, "sorRole cannot be null.");

        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 SorRole savedSorRole = this.personRepository.saveSorRole(sorRole);

        final Person person = this.personRepository.findByInternalId(sorPerson.getPersonId());
        final Role role = person.findRoleBySoRRoleId(savedSorRole.getId());
        if (role != null) {
            //update calculated role only if that role was previously converted to calculated one by sorRoleElector
            role.recalculate(savedSorRole);
            this.personRepository.savePerson(person);
        }
        //else //do nothing i.e. don't update the calculated role if SorRoleElector Previously decided not to convert this sor role to calculated role

        return new GeneralServiceExecutionResult<SorRole>(savedSorRole);
    }

    public boolean removeSorName(SorPerson sorPerson, Long nameId) {
        SorName name = sorPerson.findNameByNameId(nameId);
        if (name == null)
            return false;

        // remove name from the set (annotation in set to delete orphans)
        sorPerson.getNames().remove(name);

        // save changes
        this.personRepository.saveSorPerson(sorPerson);
        return true;
    }

    /**
     * Move All Sor Records from one person to another.
     *
     * @param fromPerson person losing sor records.
     * @param toPerson   person receiving sor records.
     * @return Result of move. Validation errors if they occurred or the Person receiving sor records.
     */
    public boolean moveAllSystemOfRecordPerson(Person fromPerson, Person toPerson) {
        // get the list of sor person records that will be moving.
        List<SorPerson> sorPersonList = personRepository.getSoRRecordsForPerson(fromPerson);

        // move each sorRecord
        for (final SorPerson sorPerson : sorPersonList) {
            moveSystemOfRecordPerson(fromPerson, toPerson, sorPerson);
        }

        Set<? extends Identifier> oldIdentifiers = fromPerson.getIdentifiers();
        Set<? extends IdCard> oldIdCards = fromPerson.getIdCards();

        this.personRepository.deletePerson(fromPerson);
        logger.info("moveAllSystemOfRecordPerson: Deleted From Person");
        for (Identifier identifier : oldIdentifiers) {

            if (toPerson.getIdentifiersByType().get(identifier.getType().getName()) == null) {
                Identifier oldIdentifierAttachedTotoPerson = toPerson.addIdentifier(identifier.getType(),
                        identifier.getValue());
                ///if type of this identifier don't exist then add this identifier as primary and not deleted

                oldIdentifierAttachedTotoPerson.setDeleted(false);
                oldIdentifierAttachedTotoPerson.setPrimary(true);
            }
            //and if this exist then add this identifier as deleted and no primary
            else {
                Identifier oldIdentifierAttachedTotoPerson = toPerson.addIdentifier(identifier.getType(),
                        identifier.getValue());
                ///if type of this identifier don't exist then add this identifier as primary and not deleted

                oldIdentifierAttachedTotoPerson.setDeleted(true);
                oldIdentifierAttachedTotoPerson.setPrimary(false);
                ;

            }

        }
        for (IdCard oldIdCard : oldIdCards) {
            if (toPerson.getPrimaryIdCard() == null) {
                toPerson.addIDCard(oldIdCard);
            } else {
                if (oldIdCard.isPrimary())
                    oldIdCard.setPrimary(false);
                toPerson.addIDCard(oldIdCard);
            }
        }

        this.personRepository.savePerson(toPerson);

        return true;
    }

    /**
     * Move one Sor Record from one person to another.
     *
     * @param fromPerson person losing sor record.
     * @param toPerson   person receiving sor record.
     * @return Success or failure.
     */
    public boolean moveSystemOfRecordPerson(Person fromPerson, Person toPerson, SorPerson movingSorPerson) {
        movingSorPerson.setPersonId(toPerson.getId());
        updateCalculatedPersonsOnMoveOfSor(toPerson, fromPerson, movingSorPerson);
        this.personRepository.savePerson(fromPerson);
        this.personRepository.savePerson(toPerson);
        this.personRepository.saveSorPerson(movingSorPerson);
        return true;
    }

    /**
     * Move one Sor Record from one person to another where the to person is not in the registry
     *
     * @param fromPerson      person losing sor record.
     * @param movingSorPerson record that is moving.
     * @return Success or failure.
     */
    public boolean moveSystemOfRecordPersonToNewPerson(Person fromPerson, SorPerson movingSorPerson) {
        Person savedPerson = recalculatePersonBiodemInfo(this.personObjectFactory.getObject(), movingSorPerson,
                RecalculationType.ADD, false);
        for (final IdentifierAssigner ia : this.identifierAssigners) {
            ia.addIdentifierTo(movingSorPerson, savedPerson);
        }
        //remove identifiers assigned to new person from old person
        fromPerson.getIdentifiers().removeAll(savedPerson.getIdentifiers());
        updateCalculatedPersonsOnMoveOfSor(savedPerson, fromPerson, movingSorPerson);
        fromPerson = this.personRepository.savePerson(fromPerson);
        savedPerson = this.personRepository.savePerson(savedPerson);
        movingSorPerson.setPersonId(savedPerson.getId());
        this.personRepository.saveSorPerson(movingSorPerson);
        return true;

    }

    /**
     * Update the calculated person data. This method and updateCalculatedPersonOnDeleteOfSor
     * need to be generalized to handle recalculations.
     *
     * @param toPerson
     * @param fromPerson
     * @param sorPerson  Adjust calculated roles...
     *                   Point prc_role to prc_person receiving role
     *                   Add the role to the set of roles in receiving prc_person
     *                   Remove role from prc person losing role
     */
    protected void updateCalculatedPersonsOnMoveOfSor(final Person toPerson, final Person fromPerson,
            final SorPerson sorPerson) {
        Assert.notNull(toPerson, "toPerson cannot be null");
        Assert.notNull(fromPerson, "fromPerson cannot be null");
        logger.info("UpdateCalculated: recalculating person data for move.");

        final List<Role> rolesToDelete = new ArrayList<Role>();

        final List<SorRole> sorRoles = new ArrayList<SorRole>(sorPerson.getRoles());
        for (final SorRole sorRole : sorRoles) {
            for (final Role role : fromPerson.getRoles()) {
                if (sorRole.getId().equals(role.getSorRoleId())) {
                    sorRoleElector.addSorRole(sorRole, toPerson);
                    rolesToDelete.add(role);
                }
            }
        }
        for (final Role role : rolesToDelete) {
            sorRoleElector.removeCalculatedRole(fromPerson, role,
                    this.personRepository.getSoRRecordsForPerson(fromPerson));
            fromPerson.getRoles().remove(role);
        }

        // TODO recalculate names for person receiving role? Anything else?
        // TODO recalculate names for person losing role? Anything else?
        //        this.personRepository.savePerson(fromPerson);
        //        this.personRepository.savePerson(toPerson);
    }

    public boolean expireRole(SorRole role) {
        role.setEnd(new Date());
        return true;
    }

    public boolean renewRole(SorRole role) {
        final Calendar cal = Calendar.getInstance();

        //TODO need to read configuration data for setting the default renewal date for this role type.
        //hard code to 6 months for now.
        cal.add(Calendar.MONTH, 6);
        role.setEnd(cal.getTime());
        return true;
    }

    protected List<PersonMatch> createMatches(final List<Person> people) {
        final List<PersonMatch> personMatches = new ArrayList<PersonMatch>();
        for (final Person person : people) {
            final PersonMatch p = new PersonMatchImpl(person, 50, new ArrayList<FieldMatch>());
            personMatches.add(p);
        }

        return personMatches;
    }

    protected Person recalculatePersonBiodemInfo(final Person person, final SorPerson sorPerson,
            final RecalculationType recalculationType, boolean mistake) {
        final List<SorPerson> sorPersons = this.personRepository.getSoRRecordsForPerson(person);
        logger.info("recalculatePersonBiodemInfo: start");
        if (recalculationType == RecalculationType.ADD
                || (recalculationType == RecalculationType.DELETE && !mistake)) {
            sorPersons.add(sorPerson);
        }

        copySorNamesToPerson(person, sorPersons);

        final Date birthDate = this.birthDateFieldElector.elect(sorPerson, sorPersons,
                recalculationType == RecalculationType.DELETE);
        final String gender = this.genderFieldElector.elect(sorPerson, sorPersons,
                recalculationType == RecalculationType.DELETE);
        final SorName preferredName = this.preferredNameFieldElector.elect(sorPerson, sorPersons,
                recalculationType == RecalculationType.DELETE);
        final SorName officialName = this.officialNameFieldElector.elect(sorPerson, sorPersons,
                recalculationType == RecalculationType.DELETE);
        final EmailAddress emailAddress = this.preferredContactEmailAddressFieldElector.elect(sorPerson, sorPersons,
                recalculationType == RecalculationType.DELETE);
        final Phone phone = this.preferredContactPhoneNumberFieldElector.elect(sorPerson, sorPersons,
                recalculationType == RecalculationType.DELETE);
        final Map<String, String> attributes = this.attributesElector.elect(sorPerson, sorPersons,
                recalculationType == RecalculationType.DELETE);
        final SorDisclosureSettings disclosure = this.disclosureFieldElector.elect(sorPerson, sorPersons,
                recalculationType == RecalculationType.DELETE);

        final String ssn = this.ssnFieldElector.elect(sorPerson, sorPersons,
                recalculationType == RecalculationType.DELETE);
        Identifier primarySSN = person.getPrimaryIdentifiersByType().get("SSN");
        //check if the elector elcted some ssn and person does have previous ssn assigned to it
        if (!org.apache.commons.lang.StringUtils.isEmpty(ssn) && primarySSN != null) {
            try {
                this.identifierChangeService.change(person.getPrimaryIdentifiersByType().get("SSN"), ssn);
            } catch (IllegalArgumentException e) {
                logger.debug(e.getStackTrace().toString());
            } //all other exception should be propogated

        }

        person.setDateOfBirth(birthDate);
        person.setGender(gender);
        person.getPreferredContactEmailAddress().update(emailAddress);
        person.getPreferredContactPhoneNumber().update(phone);
        person.calculateDisclosureSettings(disclosure);
        person.setAttributes(attributes);

        String affiliation = "";
        Type affiliationType = null;
        if (disclosure != null) {
            logger.info("after person.calculateDisclosureSettings, disclosure code : "
                    + disclosure.getDisclosureCode());
        } else {
            logger.info("Disclosure is null");
        }
        List<SorRole> sorroles = sorPerson.getRoles();
        for (SorRole role : sorroles) {
            if (role != null) {
                logger.info("Role = " + role.getTitle());
                if (role.getAffiliationType() != null) {
                    logger.info("Role desc= " + role.getAffiliationType().getDescription());
                    affiliation = role.getAffiliationType().getDescription();

                    if (person.getDisclosureSettings() != null) {
                        logger.info("recalculating disclosure setting 1...");
                        //person.getDisclosureSettings().recalculate(this.strategyRepository.getDisclosureRecalculationStrategy());
                        person.getDisclosureSettings().recalculate(
                                this.strategyRepository.getDisclosureRecalculationStrategy(), affiliation,
                                referenceRepository);
                    }
                }
            }
        }

        //SSN election is happening in the ssn identifier assigner.

        boolean preferred = false;
        boolean official = false;

        for (final Name name : person.getNames()) {
            if (!preferred && name.sameAs(preferredName)) {
                name.setPreferredName(true);
                preferred = true;
            }

            if (!official && name.sameAs(officialName)) {
                name.setOfficialName(true);
                official = true;
            }

            if (official && preferred) {
                break;
            }
        }
        logger.info("recalculatePersonBiodemInfo: end");
        //        return this.personRepository.savePerson(person);
        return person;
    }

    /**
     * Copy SorNames to Calculated Person
     *
     * @param person
     * @param sorPersons
     */
    protected void copySorNamesToPerson(final Person person, final List<SorPerson> sorPersons) {
        person.getNames().clear();

        for (final SorPerson sorPerson : sorPersons) {
            for (final SorName sorName : sorPerson.getNames()) {
                boolean alreadyAdded = false;

                for (final Name calculatedName : person.getNames()) {
                    if (calculatedName.sameAs(sorName)) {
                        alreadyAdded = true;
                        break;
                    }
                }

                if (!alreadyAdded) {
                    person.addName(sorName);
                }
            }
        }
    }

    /**
     * Current workflow for converting an SorPerson into the actual Person.
     *
     * @param reconciliationCriteria the original search criteria.
     * @return the newly saved Person.
     */
    protected Person saveSorPersonAndConvertToCalculatedPerson(
            final ReconciliationCriteria reconciliationCriteria) {
        if (!StringUtils.hasText(reconciliationCriteria.getSorPerson().getSorId())) {
            reconciliationCriteria.getSorPerson().setSorId(this.identifierGenerator.generateNextString());
        }

        logger.info("Executing new code!!!!!!");

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

        logger.info("Creating sorPerson: person_id: " + reconciliationCriteria.getSorPerson().getPersonId());
        // Save Sor Person
        final SorPerson sorPerson = this.personRepository.saveSorPerson(reconciliationCriteria.getSorPerson());
        Person savedPerson = recalculatePersonBiodemInfo(this.personObjectFactory.getObject(), sorPerson,
                RecalculationType.ADD, false);
        savedPerson = this.personRepository.savePerson(savedPerson);

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

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

        final Person newPerson = this.personRepository.savePerson(savedPerson);

        logger.info("Verifying Number of calculated Roles: " + newPerson.getRoles().size());

        // Now connect the SorPerson to the actual person
        sorPerson.setPersonId(newPerson.getId());
        this.personRepository.saveSorPerson(sorPerson);
        logger.info("Created sorPerson: person_id: " + sorPerson.getPersonId());

        return newPerson;
    }

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

    protected Person addNewSorPersonAndLinkWithMatchedCalculatedPerson(
            final ReconciliationCriteria reconciliationCriteria, final ReconciliationResult result)
            throws SorPersonAlreadyExistsException {
        Assert.isTrue(result.getMatches().size() == 1,
                "ReconciliationResult should be 'EXACT' and there should only be one person.  The result is '"
                        + result.getReconciliationType() + "' and the number of people is "
                        + result.getMatches().size() + ".");

        final Person person = result.getMatches().iterator().next().getPerson();
        return addSorPersonAndLink(reconciliationCriteria, person);
    }

    protected void setRoleIdAndSource(SorRole sorRole, String sorSource) {
        if (!StringUtils.hasText(sorRole.getSorId())) {
            sorRole.setSorId(this.identifierGenerator.generateNextString());
        }

        if (sorRole.getSystemOfRecord() == null) {
            sorRole.setSystemOfRecord(referenceRepository.findSystemOfRecord(sorSource));
        }
    }

    /**
     * Expose person repository to subclasses
     * @return
     */
    protected PersonRepository getPersonRepository() {
        return this.personRepository;
    }

    /**
     * Expose disclosure recalculation strategy to sublclasses
     * @return
     */
    protected DisclosureRecalculationStrategyRepository getDisclosureRecalculationStrategyRepository() {
        return this.strategyRepository;
    }

    /**
     * Expose reference repository to subclasses
     * @return
     */
    protected ReferenceRepository getReferenceRepository() {
        return this.referenceRepository;
    }

    public void setIdCardGenerator(IdCardGenerator idCardGenerator) {
        this.idCardGenerator = idCardGenerator;
    }
}