/********************************************************************
 * Copyright   2004-2005 EDS. All rights reserved
 ********************************************************************/
package gov.va.med.esr.service.impl;

// Java Classes
import gov.va.med.esr.common.batchprocess.AppointmentConversionResult;
import gov.va.med.esr.common.infra.ImpreciseDate;
import gov.va.med.esr.common.model.CommonEntityKeyFactory;
import gov.va.med.esr.common.model.ee.Eligibility;
import gov.va.med.esr.common.model.ee.EnrollmentDetermination;
import gov.va.med.esr.common.model.ee.ServiceConnectionAward;
import gov.va.med.esr.common.model.lookup.EligibilityType;
import gov.va.med.esr.common.model.lookup.FunctionalGroup;
import gov.va.med.esr.common.model.lookup.Gender;
import gov.va.med.esr.common.model.lookup.NameType;
import gov.va.med.esr.common.model.lookup.SSAVerificationStatus;
import gov.va.med.esr.common.model.lookup.SSNType;
import gov.va.med.esr.common.model.lookup.ServicePeriod;
import gov.va.med.esr.common.model.lookup.VAFacility;
import gov.va.med.esr.common.model.lookup.WkfCaseType;
import gov.va.med.esr.common.model.messaging.MessageLogEntry;
import gov.va.med.esr.common.model.party.Address;
import gov.va.med.esr.common.model.person.Association;
import gov.va.med.esr.common.model.person.BirthRecord;
import gov.va.med.esr.common.model.person.FullyQualifiedIdentity;
import gov.va.med.esr.common.model.person.Name;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.PersonChangeLogEntry;
import gov.va.med.esr.common.model.person.PersonLockedReason;
import gov.va.med.esr.common.model.person.PersonMergeInfo;
import gov.va.med.esr.common.model.person.PersonUnmergeInfo;
import gov.va.med.esr.common.model.person.PreferredFacility;
import gov.va.med.esr.common.model.person.SSN;
import gov.va.med.esr.common.model.person.id.PersonEntityKey;
import gov.va.med.esr.common.model.person.id.PersonIdEntityKey;
import gov.va.med.esr.common.model.person.id.PersonIdEntityKeyImpl;
import gov.va.med.esr.common.model.person.id.VPIDEntityKey;
import gov.va.med.esr.common.model.person.id.VPIDEntityKeyImpl;
import gov.va.med.esr.common.model.workload.WorkflowCaseInfo;
import gov.va.med.esr.common.persistent.messaging.MessageLogEntryDAO;
import gov.va.med.esr.common.persistent.person.PersonDAO;
import gov.va.med.esr.common.rule.service.EventRuleService;
import gov.va.med.esr.common.rule.service.MergeRuleService;
import gov.va.med.esr.service.DemographicService;
import gov.va.med.esr.service.EEResult;
import gov.va.med.esr.service.EligibilityEnrollmentService;
import gov.va.med.esr.service.IdmServiceVO;
import gov.va.med.esr.service.LookupService;
import gov.va.med.esr.service.PSDelegateService;
import gov.va.med.esr.service.PersonHelperService;
import gov.va.med.esr.service.PersonIdentityTraits;
import gov.va.med.esr.service.PersonLockedException;
import gov.va.med.esr.service.PersonMergeService;
import gov.va.med.esr.service.PersonSearchQueryInfo;
import gov.va.med.esr.service.PersonService;
import gov.va.med.esr.service.PersonUnmergeService;
import gov.va.med.esr.service.RegistryService;
import gov.va.med.esr.service.WorkflowService;
import gov.va.med.fw.hl7.HL7Message;
import gov.va.med.fw.hl7.InvalidMessageException;
import gov.va.med.fw.persistent.DAOException;
import gov.va.med.fw.persistent.MaxRecordsExceededException;
import gov.va.med.fw.persistent.NoRecordFoundException;
import org.springframework.dao.DataIntegrityViolationException;
import gov.va.med.fw.service.AbstractComponent;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.service.ServiceOptimisticLockException;
import gov.va.med.fw.service.TimeoutServiceException;
import gov.va.med.fw.service.pagination.SearchQueryInfo;
import gov.va.med.fw.service.transaction.TransactionTimestampManager;
import gov.va.med.fw.util.Reflector;
import gov.va.med.fw.util.StringUtils;
import gov.va.med.fw.util.TimeoutException;
import gov.va.med.fw.util.builder.Builder;
import gov.va.med.fw.util.builder.BuilderException;
import gov.va.med.person.idmgmt.types.MultipleBirthIndicator;
import gov.va.med.ps.model.PatientIdentifier;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.map.ListOrderedMap;
import org.apache.commons.lang.Validate;
import org.springframework.context.ApplicationContext;

import com.sun.jmx.snmp.Timestamp;

/**
 * Class that implements ESR Person oriented services and the future common
 * person services.
 *
 * @author Alex Yoon & Andrew Pach
 * @version 3.0
 */
public class PersonServiceImpl extends AbstractComponent implements PersonService {
	/**
	 * An instance of serialVersionUID
	 */
	private static final long serialVersionUID = 4813254511062147264L;

	public static final String ACTIVE_DUTY = "1";
	public static final String ALLIED_VETERAN = "2";
	public static final String COLLATERAL = "3";
	public static final String EMPLOYEE = "4";
	public static final String MILITARY_RETIREE = "5";
	public static final String NON_VETERAN_CODE = "6";
	public static final String NSC_VETERAN_CODE = "7";
	public static final String SC_VETERAN_CODE = "8";
	public static final String TRICARE = "9";

	/**
	 * An instance of personDAO
	 */
	private PersonDAO personDAO = null;

	private TransactionTimestampManager timestampManager;

    /**
     * An instance of personMergeServiceName
     */
    private String personMergeServiceName = null;

    /**
     * An instance of PersonMergeService
     */
    private PersonMergeService personMergeService = null;

    /**
     * An instance of personUnmergeServiceName
     */
    private String personUnmergeServiceName = null;

    /**
     * An instance of PersonUnmergeService
     */
    private PersonUnmergeService personUnmergeService = null;

    /**
	 * An instance of lookupService
	 */
	private LookupService lookupService = null;

	/**
	 * An instance of psDelegateService
	 */
	private PSDelegateService psDelegateService = null;

	/**
	 * An instance of PersonHelperService
	 */
	private PersonHelperService helperService = null;

	/**
	 * An instance of messageLogEntryDAO
	 */
	private MessageLogEntryDAO messageLogEntryDAO = null;

	/**
	 * An instance of siteDataBuilder
	 */
	private Builder siteDataBuilder = null;

    private String eligibilityEnrollmentServiceName = null;

    private String eventRuleServiceName = null;

    private String mergeRuleServiceName = null;

    /**
	 * An instance of psDelegateService
	 */
    //  CCR10471 ADD A PERSON
    private IdmWebServiceDelegate idmServiceDelegate = null;


    /**
     * Instance of EligibilityEnrollmentService
     */
    private EligibilityEnrollmentService eligibilityEnrollmentService = null;

    /**
     * Instance of  EventRuleService.
     */
    private EventRuleService eventRuleService = null;

    /**
     * Instance of MergeRuleService.
     */
    private MergeRuleService mergeRuleService = null;

    /**
     * An instance of registryService.
     */

    private RegistryService registryService = null;

    private DemographicService demographicService;

    private WorkflowService workflowService = null;

     /**
     * An instance of registryService Name.
     */

    private String registryServiceName = null;


    private boolean isDataClean = true;

    private boolean isUpdateIdentityTraits = true;

	/**
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
	 */

	public void afterPropertiesSet() throws Exception {
		Validate.notNull(personMergeServiceName, "Missing required personMergeServiceName");
		Validate.notNull(personUnmergeServiceName, "Missing required personUnmergeServiceName");
		Validate.notNull(lookupService, "Missing required lookup service");
		Validate.notNull(personDAO, "Missing required person DAO");
		Validate.notNull(psDelegateService,
				"Missing required PSDelegateService");
		Validate.notNull(mergeRuleServiceName);
		Validate.notNull(eligibilityEnrollmentServiceName);
		Validate.notNull(eventRuleServiceName);
		Validate.notNull(registryServiceName);
		Validate.notNull(siteDataBuilder);
		Validate.notNull(timestampManager, "A timestampManager is required");
		Validate.notNull(helperService);
	}


	public Person save(Person incomingPerson) throws ServiceException {
		return save(incomingPerson, false);
	}

	/**
	 * Persist a person into a database
	 *
	 * @param incomingPerson
	 * @param overrideLocking
	 * @throws ServiceException
	 *             Thrown if failed to persist a person
	 */
	public Person save(Person incomingPerson, boolean overrideLocking) throws ServiceException
    {
        try
        {
			Validate.notNull(incomingPerson, "A person input must not be null.");

            boolean isNewPerson = false;
			if (incomingPerson.getPersonEntityKey() == null ||
					incomingPerson.isJustAdded() ) //CCR 13437 person just added explicitly
            {
                isNewPerson = true;
            }

			// Check if person is locked
			if (!isNewPerson && !overrideLocking)
            {
				PersonIdentityTraits onFileTraits = incomingPerson.getIdentityTraits();
				if (onFileTraits == null) {
					psDelegateService.getIdentityTraits(incomingPerson.getVPIDEntityKey());
				}
				PersonLockedReason lockedReason = getPersonLockedReason(incomingPerson, onFileTraits);
				if (lockedReason != null)
                {
                    throw new PersonLockedException(lockedReason);
                }
			}



            // Save the person
            personDAO.saveObject(incomingPerson);

            //CCR 11403: remove the the person traits comparism and move the logic up stream in
            //1.	Update person traits on UI --> DemographicServiceImpl.resetSSNVerificationStatus()
            //2.	Process response of SSN Verification file to update SSN verification status  (Process SSN Verification Response in section 6.2.3)
            //3.	Add a Person (Request to Update Person Identity Traits)
            //And replaced the person traits updates with IdM Web Service calls


			// Notice we do not clone this return object that is attached to Hibernate session
			return incomingPerson;
		}
        catch (DAOException e)
        {
			throw new ServiceException("Failed to persist a person", e);
		}
	}


	public void logChanges(Set personChangeLogEntries) throws ServiceException {
		Iterator itr = personChangeLogEntries != null ? personChangeLogEntries.iterator() : null;

		// 	persist into table for each updateType
		PersonChangeLogEntry log = null;
		try {
			while(itr != null && itr.hasNext()) {
				log = (PersonChangeLogEntry) itr.next();
				log.setSubmittedDate(timestampManager.getTransactionTimestamp());
				personDAO.saveObject(log);
			}
		} catch(DAOException e) {
			throw new ServiceException("Failed to persist PersonChangeLogEntry(s)", e);
		}
	}


   /**
    * Finds the person that best fits the criteria.  Intended for unattended search requests, where only
    * a singular Person is requested.
    *
    * @param traits A subset of identity traits
    * @return The person for the traits
    */

	public Person find(PersonIdentityTraits traits) throws ServiceException {
	   VPIDEntityKey key = traits.getVpid();
	   if(key == null) {
		   // no VPID specified so ask PSIM for exact match of traits

		   // TODO: until unattended supports this, have to split it out
		   Set results = null;
		   if(traits.containsOnlySSN())
			   //CR 11776-Replacing legacy EJBs with WS
			   //composite search option was set to false
			   results = psDelegateService.attendedSearch(traits, false);  //CCR 11776- non-composite
		   else
			   results = psDelegateService.unattendedSearch(traits);  //CCR 11776 - non-composite search


		   if(results == null || results.size() == 0)
			   throw new NoRecordFoundException("PSIM search found zero records");


		   if(results.size() > 1)
			   throw new MaxRecordsExceededException(1, "Unable to retrieve a singular exact match from Idm Web Service for unattended search");


		   PersonIdentityTraits exactMatch = (PersonIdentityTraits) results.iterator().next();

		   return doGetPerson(exactMatch.getVpid(), exactMatch, false);  //CCR 11776- non-composite
	   }
	   //CR 11776-Replacing legacy EJBs with WS
	   return doGetPerson(key, traits, false);   //CCR 11776- non-composite
   }


   /**
    * Find method that only returns the Person's VPID.
    *
    * @param traits A subset of identity traits
    * @return The VPID for the person
    */

  public VPIDEntityKey findVPIDOnly(PersonIdentityTraits traits) throws ServiceException
   {
	   Set results = null;
	   if(traits.containsOnlySSN())
		   results = psDelegateService.attendedSearch(traits, false);  //CCR 11776 - non-composite
	   else
		   results = psDelegateService.unattendedSearch(traits);  //CCR 11776 - non-composite

	   if(results.size() > 1)
		   throw new MaxRecordsExceededException(1, "Unable to retrieve a singular exact match from PSIM for unattended search");

	   if(results.size() == 0)
		   throw new NoRecordFoundException("PSIM search found zero records");

	   PersonIdentityTraits exactMatch = (PersonIdentityTraits) results.iterator().next();
	   return exactMatch.getVpid();
   }


	public Person getPerson(PersonEntityKey personID) throws ServiceException {
		return getPerson(personID, null);
	}

   /**
	 * CCR11593 Eliminate multiple 1305 calls.  If person traits are passed in, no need to get traits again.
	 * @see gov.va.med.esr.service.PersonService#getPerson(gov.va.med.esr.common.model.person.id.PersonEntityKey)
	 */
	public Person getPerson(PersonEntityKey personID, PersonIdentityTraits traits) throws ServiceException {
		Validate.notNull(personID, "Null PersonEntityKey");

		try {
			// two options here, either do ugly if checking on the instance type
			// and call the appropriate doGetPerson
			// ....or let dynamic binding take care of it via reflection
			// (approach chosen)
			// depending on actual interface, the retrieve would be diffferent
			Object[] params = null;
			if (traits == null) {
				params = new Object[] { personID };
			} else {
				params = new Object[] { personID, traits, false };
			}

			Person person = (Person) Reflector.invoke(this, "doGetPerson",
					params);

			// verify against VersionedEntityKey (if specified)
			if (personID.getVersion() != null
					&& !personID.getVersion().equals(person.getVersion()))
				throw new ServiceOptimisticLockException(
						"Version number specified in PersonEntityKey does not match on file Person",null,person);

			return person;
		}
		catch (ServiceOptimisticLockException ole) {
				throw ole;
		}
		catch (Exception e) {
			throw new ServiceException("Unable to retrieve Person", e);
		}
	}




	public Person getPersonWithoutIdentityTraits(PersonEntityKey personID) throws ServiceException {
        Person person = null;
        try {
            person = (Person) personDAO.getByVPID((VPIDEntityKey) personID);
            //CCR12269 - add our own stub record if searching for a known good vpid on unknown ESR perosn
            if (person == null) {
            	this.verifyAddStubRecord((VPIDEntityKey) personID, true);
            	person = (Person) personDAO.getByVPID((VPIDEntityKey) personID);
            }
        }
        catch (DAOException ex) {
            throw new ServiceException("Failed to getPerson by pk. Invalid personId.", ex);
        } catch (java.lang.IllegalArgumentException iae) {
            throw new ServiceException("Invalid VPID", iae);
        }


        return person;
    }

	  /**
	   * Get the person with person traits of 200ESR Correlation view
	   * @param key
	   * @return Person
	   */
	public Person doGetPerson(PersonIdEntityKey key) throws ServiceException {
		//CR 11776-Replacing EJB calls
		return doGetPerson(key, null, false);
	}

	/**
	 * Internal method that is set to public access (but not in interface) so
	 * external Reflector can access it
	 */

	public Person get200ESRPerson(PersonEntityKey key) throws ServiceException {
		Person p = null;
		try {
			p = (Person) personDAO.getByKey(key);
		}
		catch (DAOException ex) {
			throw new ServiceException("Failed to getPerson by pk", ex);
		}

		if (p != null) {

			PersonIdentityTraits esrTraits = this.getESRCorrelation(p.getVPIDEntityKey());
			overlayIdentityTraits(p, esrTraits);
			p.setPersonLockedReason(getPersonLockedReason(p, esrTraits));

			if (logger.isDebugEnabled()) {
		          logger.debug("PersonServiceImpl get200eSRPerson : person: " + p);
		    }
		}

		return p;
	}

	/**
	 * Internal method that is set to public access (but not in interface) so
	 * external Reflector can access it
	 */
	public Person doGetPerson(PersonIdEntityKey key, PersonIdentityTraits identityTraits, boolean compositeCall) throws ServiceException {

		Person p = null;
		try {
			p = (Person) personDAO.getByKey(key);
		}
		catch (DAOException ex) {
			throw new ServiceException("Failed to getPerson by pk", ex);
		}

		if (p != null) {
			if(identityTraits == null) {
				if (compositeCall)

					identityTraits = psDelegateService
					.getIdentityTraitsWithCompositeCall(p.getVPIDEntityKey());

				else
					identityTraits = psDelegateService
						.getIdentityTraits(p.getVPIDEntityKey());
			}
			overlayIdentityTraits(p, identityTraits);
			p.setPersonLockedReason(getPersonLockedReason(p, identityTraits));
		}

		return p;
	}

	/**
	 * Copies data from PersonIdentityTraits object onto Person object. TODO:
	 * move this to ConversionService?
	 *
	 * @param person
	 * @param traits
	 */
	private void overlayIdentityTraits(Person person,
			PersonIdentityTraits traits)
    {
        person.setIdentityTraits(traits);

	}

	/**
	 * Internal method that is set to public access (but not in interface) so
	 * external Reflector can access it
	 */
	public Person doGetPerson(VPIDEntityKey key) throws ServiceException {
		return doGetPerson(key, null, false);
	}

	/** CCR 11758
	 * given VPIDEntityKey, get the person with composite call
	 * @param key
	 * @return
	 * @throws ServiceException
	 */

	public Person getPersonWithCompositeCall(VPIDEntityKey key) throws ServiceException {
		return doGetPerson(key, null, true);
	}

	private Person doGetPerson(VPIDEntityKey key, PersonIdentityTraits identityTraits,
			boolean compositeCall) throws ServiceException { //CCR 11758: added compositeCall flag
		if (key == null)
			return null;

		try {
			if(identityTraits == null) {
				if (compositeCall)
					identityTraits = psDelegateService.getIdentityTraitsWithCompositeCall(key);
				else
					identityTraits = psDelegateService.getIdentityTraits(key);
			}

			/* IMPORTANT:  To get ADR.PERSON, ESR must use VPID returned by PSIM for two reasons:
			 * 1) If not deprecated, we still want to make sure that the "long" form is used
			 * 2) If deprecated, we want to make sure that the correct VPID is used.  Note that in
			 * 		cases of merge and unmerge (deprecation), returned VPID could also be different
			 * 		than passed in VPID.  this is desirable (since PSIM always returns surviving VPID)
			 * 		to ensure always using the correct ADR.PERSON record
			 */

			//CCR 11403: psDelegateService.getIdentityTraits(key); used to return long vpid in ejb call, but now it
			//is replaced by web service call that returns short vpid. Make sure it's long vpid.
			VPIDEntityKey longFormAndSurvivingVPID =  CommonEntityKeyFactory.createVPIDEntityKey(((VPIDEntityKeyImpl)identityTraits.getVpid()).getLongVPID());

			Person person = personDAO.getByVPID(longFormAndSurvivingVPID);

			//CCR12269 - need to add our own stub record when searching an unknown ESR person with good vpid
			if (person == null) {
				this.verifyAddStubRecord(longFormAndSurvivingVPID, false);
				person = personDAO.getByVPID(longFormAndSurvivingVPID);
			}
			overlayIdentityTraits(person, identityTraits);
			person.setPersonLockedReason(getPersonLockedReason(person, identityTraits));
			return person;
		} catch (DAOException e) {
			throw new ServiceException("Unable to retrieve Person by VPID", e);
		}
	}

    /* (non-Javadoc)
     * @see gov.va.med.esr.service.PersonService#search(gov.va.med.esr.service.PersonSearchQueryInfo, boolean, boolean, int)
     */

	public List search(PersonSearchQueryInfo personSearchQueryInfo, boolean isAttendedMode, boolean shouldRetrieveFullPerson,
            int maxResults) throws ServiceException {
        return search(personSearchQueryInfo,isAttendedMode,shouldRetrieveFullPerson,true,maxResults);
    }

	/* (non-Javadoc)
	 * @see gov.va.med.esr.service.PersonService#search(gov.va.med.esr.service.PersonSearchQueryInfo, boolean, boolean, boolean, int)
	 */

	public List search(PersonSearchQueryInfo personSearchQueryInfo, boolean isAttendedMode, boolean shouldRetrieveFullPerson, boolean enforceSecurity, int maxResults) throws ServiceException {
		/* This search implementation is quite complex and has evolved over time, by both requirements and special casing.  This is a
		 * candidate for redesign in the future.
		 *
		 * ANY CHANGES TO THIS CODE MUST BE DONE WITH CARE!
		 */
		ListOrderedMap results = new ListOrderedMap();

		try {
			VPIDEntityKey vpid = null;
			Iterator itr = null;

			// short-circuit here....if VPID present, ignore all else and try to get Person
			if(StringUtils.isNotBlank(personSearchQueryInfo.getVpid())) {
				// incoming VPID can be in short form or long form so make sure using long form to go against ADR.PERSON table
				try {
					vpid = CommonEntityKeyFactory.createVPIDEntityKey(VPIDEntityKeyImpl.getLongVPID(personSearchQueryInfo.getVpid()));
				} catch(IllegalArgumentException e) {
					/* we don't care, this is bad VPID, nothing to do */
				}
				if(vpid != null) {
					try {
						Person person = this.retrieveSearchResultPerson(vpid, null, null);
						if(person != null) {
							//System.out.println("person value: " + person);
							results.put(person.getVPIDEntityKey(), person);
						}
					} catch(ServiceException e) {
						// check to see if this indicates that PSIM/Idm WebService did not find anything
						if(! (e.getCause() instanceof gov.va.med.person.exceptions.NoRecordFoundException))
							throw e;
					}
				}
			} else {
				// first, let's see if there are any PSIM traits being searched on
				Map traitsKeyedByVPID = null;
				boolean noMatchFromPSIM = false;
				if (!StringUtils.isBlank(personSearchQueryInfo.getGivenName())
						|| !StringUtils.isBlank(personSearchQueryInfo
								.getMiddleName())
						|| !StringUtils.isBlank(personSearchQueryInfo
								.getFamilyName())
						|| !StringUtils.isBlank(personSearchQueryInfo
								.getUnformattedSsn())
						|| !StringUtils.isBlank(personSearchQueryInfo
								.getDateOfBirth())
						|| personSearchQueryInfo.getSigi() != null
						|| !StringUtils.isBlank(personSearchQueryInfo.getGender())

						)
                {
                    // we have determined that something is being searched on in
					// MVI (IDM web Service)
					PersonIdentityTraits searchCriteriaForTraits = new PersonIdentityTraits();

					// require at least part of the name to be present
					if (!StringUtils.isBlank(personSearchQueryInfo.getFamilyName())
							|| !StringUtils.isBlank(personSearchQueryInfo
									.getMiddleName())
							|| !StringUtils.isBlank(personSearchQueryInfo
									.getGivenName())) {
						Name legalName = new Name();
						legalName.setFamilyName(StringUtils
								.trimToNull(personSearchQueryInfo.getFamilyName()));
						legalName.setGivenName(StringUtils
								.trimToNull(personSearchQueryInfo.getGivenName()));
						legalName.setMiddleName(StringUtils
								.trimToNull(personSearchQueryInfo.getMiddleName()));
						legalName.setType(lookupService
								.getNameTypeByCode(NameType.LEGAL_NAME.getName()));
						searchCriteriaForTraits.addName(legalName);
					}

	                SSN ssn = new SSN();
	                if (StringUtils.isNotEmpty(personSearchQueryInfo.getUnformattedSsn()))
	                {
	                    // Main SSN
	                    ssn.setType(getLookupService().getSSNTypeByCode(SSNType.CODE_ACTIVE.getName()));
	                    ssn.setSsnText(StringUtils.trimToNull(SSN.formatSSN(personSearchQueryInfo.getUnformattedSsn())));
	                }
	                // Add the SSN to the search criteria.  If both SSN's are blank, this will add a null SSN which
	                // won't affect the PSIM search.
	                searchCriteriaForTraits.setSsn(ssn);
	                //searchCriteriaForTraits.setSigi(personSearchQueryInfo.getSigi());

					String gender = personSearchQueryInfo.getGender();

					if (!StringUtils.isBlank(gender))
	                {
	                    searchCriteriaForTraits.setGender(lookupService.getGenderByCode(personSearchQueryInfo
	                        .getGender()));
	                }

	                String dob = personSearchQueryInfo.getDateOfBirth();
					if (!StringUtils.isBlank(dob)) {
						BirthRecord br = new BirthRecord();
						br.setBirthDate(new ImpreciseDate(dob));
						searchCriteriaForTraits.setBirthRecord(br);
					}

		    		Address permAddress = new Address();
		    		permAddress.setLine1(personSearchQueryInfo.getAddress() !=null ? personSearchQueryInfo.getAddress() : null);
		    		permAddress.setCity(personSearchQueryInfo.getCity() !=null ? personSearchQueryInfo.getCity() : null);
		    		permAddress.setState(personSearchQueryInfo.getState() !=null ? personSearchQueryInfo.getState() : null);
		    		permAddress.setZipCode(personSearchQueryInfo.getZipCode() !=null ? personSearchQueryInfo.getZipCode() : null);
		    		permAddress.setPostalCode(personSearchQueryInfo.getPostalCode() !=null ? personSearchQueryInfo.getPostalCode() : null);
		    		permAddress.setCountry(personSearchQueryInfo.getCountry() !=null ? personSearchQueryInfo.getCountry() : null);
		    		permAddress.setPhoneNumber(personSearchQueryInfo.getHomePhoneNumber() !=null ? personSearchQueryInfo.getHomePhoneNumber(): null);

		    		searchCriteriaForTraits.addAddress(permAddress);

					// execute search against IdmWSImpl (MVI)
					Set matchingTraits = null;
					//CR 11776-Replacing EJB calls
					//both attended or unattended searches are utilized from psDelegateService that uses IdmWebServiceDelegateImpl
					if(isAttendedMode) {
						if (personSearchQueryInfo.isAddAPerson()) {
							// CCR 10471 ADD A PERSON use new IdM interface
							matchingTraits = getIdmServiceDelegate().search(searchCriteriaForTraits, true);
						} else
							matchingTraits = psDelegateService.attendedSearch(searchCriteriaForTraits, enforceSecurity); //CCR 11776- non-composite search
					}
					else {
						matchingTraits = psDelegateService.unattendedSearch(searchCriteriaForTraits); //CCR 11776 - non-composite
					}

					itr = matchingTraits != null ? matchingTraits.iterator() : null;
					// build list of matches from PS
					traitsKeyedByVPID = new HashMap();
					PersonIdentityTraits match = null;
					while (itr != null && itr.hasNext()) {
						match = (PersonIdentityTraits) itr.next();
						traitsKeyedByVPID.put(match.getVpid(), match);
					}
					if(traitsKeyedByVPID.size() == 0)
						noMatchFromPSIM = true;
				}

				if(!noMatchFromPSIM) {
					// determine if we should intersect with ESR search results
					if (!StringUtils.isBlank(personSearchQueryInfo
							.getClaimFolderNumber())
							|| !StringUtils.isBlank(personSearchQueryInfo
									.getMilitaryServiceNumber())) {
						// Call the person DAO to perform the ESR search.
						List tempResults = personDAO.find(personSearchQueryInfo);
						// remove those that did not match with PSIM/MVI (IdmWS)
						Person esrPerson = null;
						itr = tempResults != null ? tempResults.iterator() : null;
						Set chosenVPIDs = new HashSet(); // needed to make sure do not return same Person more than once
						while (itr != null && itr.hasNext()) {
							esrPerson = (Person) itr.next();
							vpid = esrPerson.getVPIDEntityKey();
							if (traitsKeyedByVPID == null) { // no PSIM search
								doSearchPerson(results, maxResults, vpid, esrPerson, null, shouldRetrieveFullPerson, false);
							} else { // yes PSIM search, do intersection logic
								// ...but first make sure have correct ADR.PERSON and VPID (recall deprecation)
								//CCR 11776- no need to check for deprecation since IdmWSImpl returns only surviving traits.
								//esrPerson = verifyNonDeprecatedSearchResult(esrPerson, vpid, null);
								//vpid = esrPerson.getVPIDEntityKey();

								// CCR 11776- retrieve traits and overlay correct traits onto correct person
								esrPerson = retrieveTraitsAndOverlayWithPerson(esrPerson, vpid);
								vpid = esrPerson.getVPIDEntityKey(); //updated vpid in case search was done by a deprecated one
								if (traitsKeyedByVPID.containsKey(vpid) && !chosenVPIDs.contains(vpid)) {
									chosenVPIDs.add(vpid);
									doSearchPerson(results, maxResults, vpid, esrPerson, (PersonIdentityTraits) traitsKeyedByVPID.get(vpid),
											shouldRetrieveFullPerson, false);
								}
							}
						}
					}
					// no ESR data to search on, just build from PSIM matches (if any)
					else {
						itr = traitsKeyedByVPID != null ? traitsKeyedByVPID.keySet()
								.iterator() : null;

						// ESR_CodeCR 7372: while processing VBA message,  need to add
						// logic to handle PSIM returning deprecated identity traits info.  When that happens,
						// query PSIM to retrive the surviving traits using the deprecated vpid.  Then collapse
						// the result records, returning only the surviving one.

						//CCR 11776 - no need to check for deprecated traits as Idm Web service returns surviving traits
						Set chosenVPIDs = new HashSet(); // needed to make sure do not return same Person more than once
						while (itr != null && itr.hasNext()) {
							vpid = (VPIDEntityKey) itr.next();

							PersonIdentityTraits matchingTraits = (PersonIdentityTraits) traitsKeyedByVPID.get(vpid);
							//CR 11776- no need to verify if traits is deprecated as IdmWS returns surviving traits
							//PersonIdentityTraits survivingTraits = verifyNonDeprecatedIdentityTraits(vpid, matchingTraits);
							//VPIDEntityKey survivingVpid = survivingTraits.getVpid();

							VPIDEntityKey survivingVpid = matchingTraits.getVpid();

							if (!chosenVPIDs.contains(survivingVpid)) {
								chosenVPIDs.add(survivingVpid);
								doSearchPerson(results, maxResults, survivingVpid, null, matchingTraits,      //CCR11776- survivingTratis is same as matchingTraits
											shouldRetrieveFullPerson, personSearchQueryInfo.isAddAPerson());
							}
						}
					}
				}
			}
		} catch (DAOException e) {
			throw new ServiceException(
					"Unable to execute PersonDAO search against ADR", e);
		}

		// Set resultant information to the caller per the
		// PaginatedSearchService contract
		personSearchQueryInfo.setTotalNumberOfEntries(results.size());
		personSearchQueryInfo.setSearchTypePerformed(SearchQueryInfo.SEARCH_READ_ALL);
		personSearchQueryInfo.setSortPerformed(false);

		return results.valueList();
	}
	/**
	 * ADD A PERSON: add a new person to MVI and also add a person stub in ADR and set the vpid and person id for incoming person
	 *
	 * @param incoming  incoming Person to be added
	 * @return the vpid added
	 * @throws ServiceException
	 */
	//CCR 12789 - added for anonymous and level 1
	public VPIDEntityKey addPersonExplicit(Person incoming, IdMSearchInfo searchInfo) throws ServiceException {
		Validate.notNull(incoming, "Add Person Explicitly with a NULL Person object");
		Validate.notNull(searchInfo, "Add Person Explicitly with a NULL IdMSearchInfo object");
		Validate.isTrue(searchInfo.isAddPersonExplicit(), "Add Person Explicitly with a false addPersonExplicit flag");

		IdmServiceVO idmServiceVO = this.convertPersonToIdmServiceVO(incoming);

		//add a person to MVI
		VPIDEntityKey vpidEntityKey = getIdmServiceDelegate().addPerson(idmServiceVO, searchInfo);

		//add a person stub to ADR with veteran indicator
        PersonEntityKey personKey = null;
        if (vpidEntityKey != null) {
            personKey = this.addPersonStub(vpidEntityKey, incoming.getVeteran(), incoming.getVOAIndicator());
            if (personKey == null)
           	 throw new ServiceException ("Failed to add person stub record for VPID:" + vpidEntityKey.getKeyValueAsString());

            //need to flush the person so that later operations (e.g. create workload case) can use the person
            try {
            	this.getPersonDAO().flush();
            } catch (DAOException dex) {
            	throw new ServiceException ("Failed to add person stub explicit and flush record for VPID:"
            			+ vpidEntityKey.getKeyValueAsString(), dex);
            }
        }

		//set the vpid for incoming person
        //ESR internally needs long vpid because person table supports only long vpid
		String vpid = VPIDEntityKeyImpl.getLongVPID(vpidEntityKey.getKeyValueAsString());
		incoming.setVPIDValue(vpid);

		//set the person id for incoming person
        incoming.setIdentifier(personKey != null ? (BigDecimal)personKey.getKeyValue() : null);

        return vpidEntityKey;
	}

	/**
	 * ADD A PERSON: send traits information to
	 *
	 * @param traits  Person Identity Traits data
	 * @return vpid
	 * @throws ServiceException
	 */
	public VPIDEntityKey addPerson(IdmServiceVO idmServiceVO) throws ServiceException {
	    //CCR12192: PSIM no longer creating stub record in ADR when
        //MVI addPerson/addCorrelation service method is called

        VPIDEntityKey vpidEntityKey = getIdmServiceDelegate().addPerson(idmServiceVO, null);

        PersonEntityKey personKey = null;
        if (vpidEntityKey != null) {
            personKey = this.addPersonStub(vpidEntityKey);
            if (personKey == null)
           	 throw new ServiceException ("Failed to add person stub record for VPID:" + vpidEntityKey.getKeyValueAsString());
        }
        return vpidEntityKey;
	}


	public PersonIdentityTraits getESRCorrelation(VPIDEntityKey vpid) throws ServiceException  {
		return  getIdmServiceDelegate().getESRCorrelation(vpid);
	}

	/**
	 * Add ESR Correlation in PSIM
	 *
	 * @param onFile Person on file contains ESR Correlation flag indicator to determine if ESR Correlation Add is needed
     * @param incomeing Person contains the address populated so that Add ESR Correlation will send address to PSIM
     *
	 * @return onFile Person
	 */
	public Person addESRCorrelation(Person onFile, Person incoming) throws ServiceException {
		try {

	        //CCR 11403: for Vista inbound messages, check enrollment records, add 200ESRCorreltion if necessary
            //CCR 11758
			if (!onFile.isEsrCorrelationAdded()) {
				//Add ESR Correlation using incoming person with address populated
				addESRCorrelation(incoming);
				onFile.setEsrCorrelationAdded(true); //CCR 12176 - incoming message with new person that ESR correlation is just added
			}

	        return onFile;
		} catch (Exception e) {
			throw new RuntimeException(
					"Failed to add ESR Correlation in PSIM", e);
		}
	}

	/**
	 * Adds ESR correlation for person, updating the profile with the person identity trait information
	 * @param person
	 * @return VPIDEntityKey that is created
	 * @throws ServiceException
	 */

	public VPIDEntityKey addESRCorrelation(Person incomingPerson) throws ServiceException {
		//CCR12192: PSIM no longer creating stub record in ADR when
        //MVI addPerson/addCorrelation service method is called

        VPIDEntityKey vpidKey = null;

        //check if person exists in MVI first
        PersonIdentityTraits identityTraits =
                psDelegateService.getIdentityTraitsWithCompositeCall(incomingPerson.getVPIDEntityKey());
        if (identityTraits != null) {
        	vpidKey =  CommonEntityKeyFactory.createVPIDEntityKey
                    (((VPIDEntityKeyImpl)identityTraits.getVpid()).getLongVPID());
        }


        //searched with composite call so we can use has200ESRCorrelation() here
        if (vpidKey != null && "N".equals(identityTraits.has200ESRCorrelation())) {

        	PersonEntityKey personKey = this.addPersonStub(vpidKey);
             if (personKey == null)
            	 throw new ServiceException ("Failed to add person stub record for VPID:" + vpidKey.getKeyValueAsString());

      		//CCR12147 - addESRCorrelation implicitly means the ssn verification status is NEW
      		//set it in the incoming person so that the update to 200ESR correlation will not wipe out the NEW status
      		if (incomingPerson.getOfficialSsn() != null)
      			incomingPerson.getOfficialSsn().setSsaVerificationStatus(
      					this.getLookupService().getSSAVerificationStatusByCode(SSAVerificationStatus.NEW_RECORD.getCode()));

      		vpidKey = getIdmServiceDelegate().addCorrelation(convertPersonToIdmServiceVO(incomingPerson));

      		 //CCR12710
            if (logger.isDebugEnabled()) {
    	          logger.debug("PersonServiceImpl addCorrelation : 200ESR added for vpid: " + vpidKey);
    	    }
        }



        return vpidKey;
	}

	/**
	 * Check to see if ESR200 Correlation exists for the person and Adds ESR correlation if necessary
	 * @param person
	 * @return
	 *
	 * NOTE: The most correct way to do it is to check 200ESR correlation (get corresponding Ids   1309),
	 * add 200ESR Correlation (update profile   1302) if it doesn t exist.
	 * To reduce number of IdM Web Service calls, Laurie has confirmed that if we can correct those 7859 migrated records,
	 * every ESR record with enrollment data is guaranteed to have 200ESR Correlation
	 */
	public void checkAndAddESRCorrelation(Person person) throws ServiceException
	{
        //CCR 11403: for Vista inbound messages, check enrollment records, add 200ESRCorreltion if necessary
        if (person != null &&
        	(person.getEnrollmentDeterminations() == null || person.getEnrollmentDeterminations().size() == 0))
        {
        	this.addESRCorrelation(person);
        }
	}

	/**
	 * Update Profile for ESR correlation
	 * @param idmServiceVO
	 * @throws ServiceException
	 */

	public void updateProfileForESRCorrelation(IdmServiceVO idmServiceVO) throws ServiceException {
		getIdmServiceDelegate().updatePerson(idmServiceVO);
	}

	/**
	 * Update Profile for ESR correlation
	 * @param person
	 * @throws ServiceException
	 */

	public void updateProfileForESRCorrelation(Person incomingPerson) throws ServiceException {
		updateProfileForESRCorrelation(convertPersonToIdmServiceVO(incomingPerson));
	}

	/**
	 * Update Profile for Death Event
	 * @param person
	 * @throws ServiceException
	 */

	public void updateProfileForDeathEvent(Person incomingPerson) throws ServiceException {
		IdmServiceVO idmServiceVo = convertPersonToIdmServiceVO(incomingPerson);
		idmServiceVo.setDeathEvent(true);
		updateProfileForESRCorrelation(idmServiceVo);
	}

	/**
	 * Update Profile for AAP, update person 1302
	 * removes death record if entered during initial registration
	 * @param person
	 * @throws ServiceException
	 */

	public void updateProfileForAAP(Person incomingPerson) throws ServiceException {
		IdmServiceVO idmServiceVo = convertPersonToIdmServiceVO(incomingPerson);
		idmServiceVo.setDeathRecord(null);
		updateProfileForESRCorrelation(idmServiceVo);
	}

	/**
	 * Add correlation for preferred facility for a person who already has MVI correlations available
	 * @param incomingPerson
	 * @param list of correlations from MVI
	 * @throws ServiceException
	 */
	public FullyQualifiedIdentity addPreferredFacilityCorrelation(Person incomingPerson, List<PatientIdentifier> correlations) throws ServiceException
	{
		// verify that we do have a preferred facility, and add correlation for the sending
		// site related to this facility

		// since the incoming person is new, the mostRecentPreferredFacility would not have been calculated previously
		// just get the only one allowed for ESR user input
		PreferredFacility incomingPF = null;


		if (incomingPerson.getMostRecentPreferredFacility() != null) {
			incomingPF = new PreferredFacility();
			incomingPF.setFacility(incomingPerson.getMostRecentPreferredFacility());
		}


		if (incomingPF == null && ! incomingPerson.getPreferredFacilities().isEmpty()) {
			Iterator iter = incomingPerson.getPreferredFacilities().iterator();
			incomingPF = (PreferredFacility)iter.next();
		}

		if (incomingPF != null && incomingPF.getFacility() != null) {

			// use only primary facility when adding preferred facility correlation in idm
			VAFacility primaryFacility = getParentSite(incomingPF.getFacility());

			// check for existing correlations for this person, make sure we don't already have this correlation

			if (correlations == null) {
				//CR 11776-Replacing EJB calls
				correlations = getIdmServiceDelegate().getCorrelationsByVPID(incomingPerson.getVPIDEntityKey());
			}

			if (correlations != null) {
				String personStationNumber = primaryFacility.getStationNumber();
				PatientIdentifier patientId = null;
				String correlationStationNumber = null;

				for (Iterator iter=correlations.iterator(); iter.hasNext(); ) {
					patientId = (PatientIdentifier) iter.next();
					correlationStationNumber = patientId.getStationNumber();
					if (personStationNumber != null && personStationNumber.equals(correlationStationNumber)) {
						// match found, no need to add preferred facility correlation
						return new FullyQualifiedIdentity(patientId.getIdentity(), correlationStationNumber);
					}
				}
			}
			 //CCR12710
	        if (logger.isDebugEnabled()) {
		          logger.debug("PersonServiceImpl APTF : facility correlation adding for: " + incomingPerson);
		    }
			// add preferred facility correlation
			//CR 11776-Replacing EJB calls
	        //CCR 13884 - default the gender if missing or invalid
	        IdmServiceVO vo = convertPersonToIdmServiceVO(incomingPerson);

	        if (vo.getGender() != null ) {

	        	if ((!vo.getGender().getCode().equalsIgnoreCase(Gender.MALE.getCode()))
	        			&& (!vo.getGender().getCode().equalsIgnoreCase(Gender.FEMALE.getCode()))) {

	        		logger.info("Defaulting UNK Gender to M for APTF on person:" + incomingPerson.getPersonEntityKey().getKeyValueAsString());

	        		vo.setGender(lookupService.getGenderByCode(Gender.MALE
						.getName()));
	        	}
	        } else {

	        	logger.info("Defaulting NULL Gender to M for APTF on person:" + incomingPerson.getPersonEntityKey().getKeyValueAsString());

        		vo.setGender(lookupService.getGenderByCode(Gender.MALE
					.getName()));

	        }
			return getIdmServiceDelegate().addPreferredFacility(vo);
		}
		else {
			throw new ServiceException("Cannot add preferred facility, incomingPF is null");
		}
	}


	/**
	 * Add correlation for preferred facility for a new person
	 * @param traits PersonIdentityTraits
	 * @param key
	 * @throws ServiceException
	 */

	public FullyQualifiedIdentity addPreferredFacilityCorrelation(Person incomingPerson) throws ServiceException {
		return this.addPreferredFacilityCorrelation(incomingPerson, null);
	}

	/**
	 * Find the parent sending site among the preferred facility or its parent/ancestors
	 * @param preferredFacility
	 * @return
	 * @throws ServiceException
	 */

	public VAFacility getParentSite(VAFacility preferredFacility)
			throws ServiceException {
		if (preferredFacility == null)
			return null;

		VAFacility facility = preferredFacility;

		if (Boolean.TRUE.equals(facility.isMfnzegRecipient())) {
			return facility;
		}

        /**
         * there is no parent so. this must be parent.
         */
        if (facility.getParentId() == null ) {
              return facility;
        }

		// go up the parent tree until we exhaust the parent or find a sending site
		while (facility.getParentId() != null
				&& !(facility.getParentId().compareTo(facility.getIdentifier()) == 0)) {
			facility = this.getLookupService().getVAFacilityById(
					facility.getParentId());
			if (Boolean.TRUE.equals(facility.isMfnzegRecipient())) {
				return facility;
			}
		}
		// sending site of the preferred facility not found
		throw (new ServiceException("Sending site for facility "
				+ preferredFacility.getStationNumber() + " cannot be found"));
	}

	private Person doSearchPerson(ListOrderedMap results, int maxResults, VPIDEntityKey vpid, Person alreadyRetrievedPartialPerson, PersonIdentityTraits identityTraits, boolean
			shouldRetrieveFullPerson, boolean isAddPerson) throws ServiceException {
		Person retrievedPerson = null;
		// check against maxResults
		if(maxResults > 0 && results != null && results.size() >= maxResults) {
			throw new MaxRecordsExceededException(maxResults, "Search exceeded requested limit of " + maxResults + " Persons");
		}

		if(shouldRetrieveFullPerson) {
			//CR 11776-Replacing EJB calls, passed compositeCall value to false
			retrievedPerson = doGetPerson(vpid, identityTraits, false);
		} else {
			if (isAddPerson) {
				//System.out.println("PersonServiceImpl::doSearchPerson()-> convert PersonIdentityTraits to person object (before)=" + identityTraits);
				// CR 10471 Convert the PersonIdentityTraits to person object
				retrievedPerson = convertTraitsToPerson(identityTraits);
				//System.out.println("PersonServiceImpl::doSearchPerson()-> convert PersonIdentityTraits to person object(after)=" + retrievedPerson);

			} else {
				retrievedPerson = retrieveSearchResultPerson(vpid, alreadyRetrievedPartialPerson, identityTraits);
			}
		}

		results.put(retrievedPerson.getVPIDEntityKey(), retrievedPerson);
		return retrievedPerson;
	}

	/**
	 * Searches for a list of partially populated Person objects based on the passed in search query
	 * information.  This is intended for attended usage (eg, UI).
	 *
	 * @param searchQueryInfo
	 *            The search information.
	 * @return The list of partially populated Person objects.  This is ideal for attended usage (eg, UI) where the full
	 * Person is not required for displaying search results
	 * @throws ServiceException
	 *             if there were any problems performing the search.
	 */

	public List search(SearchQueryInfo searchQueryInfo) throws ServiceException {
		// Ensure we have the correct type of query information object
		if (!(searchQueryInfo instanceof PersonSearchQueryInfo)) {
			throw new ServiceException("searchQueryInfo is an instance of '"
					+ searchQueryInfo.getClass().getName()
					+ "' and must be an instance of 'PersonSearchQueryInfo'.");
		}
		return search((PersonSearchQueryInfo) searchQueryInfo, true, false, -1);
	}

	/**
	 * Since the search results logic is multi-dimensional based on what is
	 * already known (depends on the execution flow), it was best to isolate to
	 * this method rather than "dirty up" the doGetPerson(VPIDEntityKey) method.
	 *
	 * @param vpid
	 * @param incomingPerson
	 * @param traits
	 * @return
	 * @throws ServiceException
	 */
	private Person retrieveSearchResultPerson(VPIDEntityKey vpid,
			Person incomingPerson, PersonIdentityTraits traits)
			throws ServiceException {
		Validate.notNull(vpid, "Need a non-null VPID");

		Person searchResultPerson = null;
		try {
			if (incomingPerson == null) {
				// this needs to populate VPID on person
				searchResultPerson = personDAO.getSearchResultsByVPID(vpid);
				//CCR12269 - need to add our own stub when searching an unknown ESR person with good known vpid
				if (searchResultPerson == null) {
					verifyAddStubRecord(vpid, true);
					searchResultPerson = personDAO.getSearchResultsByVPID(vpid);
				}

			}
			else {
				searchResultPerson = incomingPerson;
			}
			/* CCR12269 - this is dead code now with above check
			if (searchResultPerson == null)
				throw new ServiceException(
						"Unable to retrieve Person with VPID: " + vpid.getKeyValueAsString() + " - Perhaps there is a mismatch with ESR and PSIM data");
			*/

			//CR 11776-Replacing EJB calls
			//this method returns traits that are surviving only. if a deprecated vpid entered, returns the surviving traits.
			if (traits == null){
				traits = psDelegateService.getIdentityTraits(vpid);
				//System.out.println("search traits: " + traits);
			}

			/*
			 * At this point, it could be possible for ESR to have a deprecated ADR.PERSON with deprecated VPID/traits.
			 * However, ESR will not know it is deprecated until PSIM returns traits and we compare passed in VPID
			 * with resultant VPID.  If they differ, must re-retrieve the ADR.PERSON to get the correct ADR.PERSON.
			 */


			//CR 11776-Replacing EJB calls
			//No need to check whether traits is deprecated as the getIdentityTraits retunrs a non deprecated/surviving traits
			//searchResultPerson = verifyNonDeprecatedSearchResult(searchResultPerson, vpid, traits); // could change person or person+traits
			//traits = searchResultPerson.getIdentityTraits(); // reset traits local variable....guarantees these are the correct traits

			// overlay correct traits onto correct person
			overlayIdentityTraits(searchResultPerson, traits);

			searchResultPerson.setPersonLockedReason(getPersonLockedReason(searchResultPerson, traits));

			// CCR 10471 ADD A PERSON, does not work because traits data are reset.
			//			if (traits.isAddAPerson() && searchResultPerson.getEnrollmentDetermination() == null ) {
			//				searchResultPerson.setPersonLockedReason(null);
			//			}

		}
		catch (DAOException e) {
			throw new ServiceException( "Unable to retrieve search result by VPID for ESR", e);
		}

		return searchResultPerson;
	}



	// overlay correct traits onto correct person
	private Person retrieveTraitsAndOverlayWithPerson(Person person, VPIDEntityKey vpid) throws ServiceException {

		PersonIdentityTraits traits = psDelegateService.getIdentityTraits(vpid);
		try {
			// use new VPID to get correct ADR.PERSON record
			person = personDAO.getSearchResultsByVPID(traits.getVpid());
			//CCR12260 - create our own stub record if searching an unknown person with good known vpid
			if (person == null) {
				this.verifyAddStubRecord(traits.getVpid(), false);
				person = personDAO.getSearchResultsByVPID(traits.getVpid());
			}
		} catch (DAOException e) {
			throw new ServiceException( "Unable to retrieve Person by VPID for ESR", e);
		}
		// overlay correct traits onto correct person
		overlayIdentityTraits(person, traits);

		return person;
	}



	/**  This is the "cleansing" method that all searches must go through to guarantee that the Person and PersonIdentityTraits
	 * do not represent a deprecated Person, as this design does not allow deprecated Persons to even be seen on the search
	 * results.
	 *
	 * @param person
	 * @param vpid
	 * @param traits
	 * @return
	 * @throws ServiceException
	 */


	//CR 11776-Replacing EJB calls
	private Person verifyNonDeprecatedSearchResult(Person person, VPIDEntityKey vpid, PersonIdentityTraits traits) throws ServiceException {
		Person nonDeprecatedPerson = person;

		// retrieve the surviving identity traits in case incoming traits are deprecated.
		traits = verifyNonDeprecatedIdentityTraits(vpid, traits);


		try {
			if(traits.isRetrievedWithDeprecatedVPID()) {
				if(logger.isWarnEnabled())
					logger.warn("Search Result Person found with deprecated VPID [" + vpid.getKeyValueAsString() +
							"] - must re-retrieve Search Result Person with surviving VPID [" + traits.getVpid().getKeyValueAsString() + "]");
				nonDeprecatedPerson = personDAO.getSearchResultsByVPID(traits.getVpid()); // use new VPID to get correct ADR.PERSON record
			}
			if (nonDeprecatedPerson == null) {
				//CCR12260-add our own person stub record if searching an unknown person with good vpid
				this.verifyAddStubRecord(traits.getVpid(), false);
				nonDeprecatedPerson = personDAO.getSearchResultsByVPID(traits.getVpid());
			}
		} catch (DAOException e) {
			throw new ServiceException( "Unable to retrieve Person by VPID for ESR", e);
		}

		// overlay correct traits onto correct person
		overlayIdentityTraits(nonDeprecatedPerson, traits);

		return nonDeprecatedPerson;
	}


	/**  Similar to verifyNonDeprecatedSearchResult, but instead only returns the surviving identity traits
	 *  instead of the Person record.
	 *
	 * @param vpid
	 * @param traits
	 * @return
	 * @throws ServiceException
	 */

	private PersonIdentityTraits verifyNonDeprecatedIdentityTraits(VPIDEntityKey vpid, PersonIdentityTraits traits) throws ServiceException {

		PersonIdentityTraits nonDeprecatedTraits = traits;

		if (traits == null || traits.isDeprecated())
			//CR 11776 - Replacing EJB calls
			// in the case of deprecated traits, will get the surviving traits
			nonDeprecatedTraits = psDelegateService.getIdentityTraits(vpid);

		return nonDeprecatedTraits;
	}


	/**
	 * @see gov.va.med.esr.service.PersonService#getPersonFromSiteData(gov.va.med.esr.common.model.person.id.PersonEntityKey, java.lang.String)
	 */

	public Person getPersonFromSiteData(PersonEntityKey personID, String status) throws ServiceException {

		Person siteData = null;

		if( logger.isDebugEnabled() ) {
			logger.debug( "Searching for site data with the specific person id " + personID );
			logger.debug( "Received eligibility status from a site" + status );
		}

		// Retrieve a person from a person id
		Person person = this.getPerson( personID );

		// Get a site message log entry for the specific person
		try {
			HL7Message siteMessage = null;
			MessageLogEntryDAO dao = this.getMessageLogEntryDAO();
			MessageLogEntry mle = dao.findByReceivedEligiblityStatus(person,status);
			if( mle != null ) {
				siteMessage = new HL7Message( mle.getBody() );
			}
			// Overlay a person with data from a site data message
			if( siteMessage != null ) {
				Builder builder = this.getSiteDataBuilder();
				siteData = (Person)builder.build( new Object[]{ (Person)person.clone(), siteMessage} );
            siteData.setSendingFacility( siteMessage.getSendingFacility() );
			}
		}
		catch( DAOException e ) {
			if( logger.isDebugEnabled() ) {
				logger.debug( "Failed to retrieve a site data due to exception: ", e);
			}
			throw new ServiceException( "Failed to retrieve a site data due to exception: ", e );
		}
		catch( InvalidMessageException e ) {
			if( logger.isDebugEnabled() ) {
				logger.debug( "Failed to parse an original message ", e);
			}
			throw new ServiceException( "Failed to parse an original message ", e );
		}
		catch( BuilderException e ) {
			if( logger.isDebugEnabled() ) {
				logger.debug( "Failed to build a person from site data", e);
			}
			throw new ServiceException( "Failed to build a person from site data", e );
		}
		return siteData;
	}

	/**
	 * @return Returns the personDAO.
	 */
	public PersonDAO getPersonDAO() {
		return personDAO;
	}

	/**
	 * @param personDAO
	 *            The personDAO to set.
	 */
	public void setPersonDAO(PersonDAO personDAO) {
		this.personDAO = personDAO;
	}

    /**
     * @return Returns the personMergeServiceName.
     */
    public String getPersonMergeServiceName()
    {
        return personMergeServiceName;
    }

    /**
     * @param personMergeServiceName The personMergeServiceName to set.
     */
    public void setPersonMergeServiceName(String personMergeServiceName)
    {
        this.personMergeServiceName = personMergeServiceName;
    }

    public PersonMergeService getPersonMergeService()
    {
    	if (personMergeService == null)
    		personMergeService = (PersonMergeService) getApplicationContext()
		.getBean(personMergeServiceName, PersonMergeService.class);

    	return personMergeService;
    }

    /**
     * @return Returns the personUnmergeServiceName.
     */
    public String getPersonUnmergeServiceName()
    {
        return personUnmergeServiceName;
    }

    /**
     * @param personUnmergeServiceName The personUnmergeServiceName to set.
     */
    public void setPersonUnmergeServiceName(String personUnmergeServiceName)
    {
        this.personUnmergeServiceName = personUnmergeServiceName;
    }

    public PersonUnmergeService getPersonUnmergeService()
    {
    	if (personUnmergeService == null)
    		personUnmergeService = (PersonUnmergeService) getApplicationContext()
		.getBean(personUnmergeServiceName, PersonUnmergeService.class);

    	return personUnmergeService;
    }



    /**
     * @return Returns the EligibilityEnrollmentService.
     */
    public EligibilityEnrollmentService getEligibilityEnrollmentService() {
        if (this.eligibilityEnrollmentService == null)
            this.eligibilityEnrollmentService = (EligibilityEnrollmentService) getApplicationContext()
                .getBean(this.getEligibilityEnrollmentServiceName(), EligibilityEnrollmentService.class);
        return this.eligibilityEnrollmentService;
    }



    /**
     * @return Returns the eligibilityEnrollmentServiceName.
     */
    public String getEligibilityEnrollmentServiceName() {
        return eligibilityEnrollmentServiceName;
    }

    /**
     * @param eligibilityEnrollmentServiceName The eligibilityEnrollmentServiceName to set.
     */
    public void setEligibilityEnrollmentServiceName(
            String eligibilityEnrollmentServiceName) {
        this.eligibilityEnrollmentServiceName = eligibilityEnrollmentServiceName;
    }

    /**
     * @return Returns the eventRuleService.
     */
    public EventRuleService getEventRuleService() {
        if (eventRuleService == null)
            eventRuleService = (EventRuleService) getApplicationContext()
                .getBean(this.getEventRuleServiceName(), EventRuleService.class);
        return eventRuleService;
    }


    /**
     * @return Returns the eventRuleServiceName.
     */
    public String getEventRuleServiceName() {
        return eventRuleServiceName;
    }

    /**
     * @param eventRuleServiceName The eventRuleServiceName to set.
     */
    public void setEventRuleServiceName(String eventRuleServiceName) {
        this.eventRuleServiceName = eventRuleServiceName;
    }

    /**
     * @return Returns the mergeRuleService.
     */
    public MergeRuleService getMergeRuleService() {
        if (mergeRuleService == null)
            mergeRuleService = (MergeRuleService) getApplicationContext()
                .getBean(this.getMergeRuleServiceName(), MergeRuleService.class);
        return mergeRuleService;
    }


    /**
     * @return Returns the mergeRuleServiceName.
     */
    public String getMergeRuleServiceName() {
        return mergeRuleServiceName;
    }

    /**
     * @param mergeRuleServiceName The mergeRuleServiceName to set.
     */
    public void setMergeRuleServiceName(String mergeRuleServiceName) {
        this.mergeRuleServiceName = mergeRuleServiceName;
    }

//    /**
//     * @param personMergeService The personMergeService to set.
//     */
//    public void setPersonMergeService(PersonMergeService personMergeService) {
//        this.personMergeService = personMergeService;
//    }

    /**
     * @param personUnmergeService The personUnmergeService to set.
     */
    public void setPersonUnmergeService(PersonUnmergeService personUnmergeService) {
        this.personUnmergeService = personUnmergeService;
    }

    /**
	 * @return Returns the lookupService.
	 */
	public LookupService getLookupService() {
		return lookupService;
	}

	/**
	 * @param lookupService
	 *            The lookupService to set.
	 */
	public void setLookupService(LookupService lookupService) {
		this.lookupService = lookupService;
	}

	/**
	 * @return Returns the psDelegateService.
	 */
	public PSDelegateService getPsDelegateService() {
		return psDelegateService;
	}

	/**
	 * @param psDelegateService
	 *            The psDelegateService to set.
	 */
	public void setPsDelegateService(PSDelegateService psDelegateService) {
		this.psDelegateService = psDelegateService;
	}

	/**
	 * @return Returns the siteDataBuilder.
	 */
	public Builder getSiteDataBuilder() {
		return siteDataBuilder;
	}

	/**
	 * @param siteDataBuilder The siteDataBuilder to set.
	 */
	public void setSiteDataBuilder(Builder siteDataBuilder) {
		this.siteDataBuilder = siteDataBuilder;
	}

	/**
	 * @return Returns the messageLogEntryDAO.
	 */
	public MessageLogEntryDAO getMessageLogEntryDAO() {
		return messageLogEntryDAO;
	}

	/**
	 * @param messageLogEntryDAO The messageLogEntryDAO to set.
	 */
	public void setMessageLogEntryDAO(MessageLogEntryDAO messageLogEntryDAO) {
		this.messageLogEntryDAO = messageLogEntryDAO;
	}


	public void processExternalPersonLink(gov.va.med.esr.service.external.person.PersonLinkInfo linkInfo) throws ServiceException {
        if (logger.isInfoEnabled())
            logger.info("ESR received an external person link event: "
                    + linkInfo);

		/* Current the ESR implementation of HealtheVet "link" is to treat as a "merge" */
		PersonMergeInfo info = new PersonMergeInfo();

        VPIDEntityKey deprecatedVPID = CommonEntityKeyFactory.createVPIDEntityKey(linkInfo.getDeprecatedVPID().getVPID());
        VPIDEntityKey survivingVPID = CommonEntityKeyFactory.createVPIDEntityKey(linkInfo.getSurvivingVPID().getVPID());

        PersonIdEntityKey deprecatedPersonIdEntityKey = this.getPersonIdByVPID(deprecatedVPID);
        PersonIdEntityKey survivingPersonIdEntityKey = this.getPersonIdByVPID(survivingVPID);

		//PersonIdEntityKey deprecatedPersonIdEntityKey = CommonEntityKeyFactory.createPersonIdEntityKey(doGetPerson(deprecatedVPID).getPersonEntityKey().getKeyValueAsString());
		//PersonIdEntityKey survivingPersonIdEntityKey = CommonEntityKeyFactory.createPersonIdEntityKey(doGetPerson(survivingVPID).getPersonEntityKey().getKeyValueAsString());
		if(deprecatedPersonIdEntityKey == null)
			throw new ServiceException("Unable to retrieve Person for deprecatedVPID: " + linkInfo.getDeprecatedVPID());
		if(survivingPersonIdEntityKey == null)
			throw new ServiceException("Unable to retrieve Person for survivingVPID: " + linkInfo.getSurvivingVPID());

		//WI193140, deprecated_id same Id as primary; do not process merge event
		//as primary id gets flagged in the isDeprecated flow

		if (deprecatedPersonIdEntityKey.getKeyValueAsString().equals(survivingPersonIdEntityKey.getKeyValueAsString())) {
			throw new ServiceException("Unable to process merge for identical Person_ids for surviving vpid: " + linkInfo.getSurvivingVPID());
		}

		info.setDeprecatedPersonEntityKey(deprecatedPersonIdEntityKey);
		info.setPrimaryPersonEntityKey(survivingPersonIdEntityKey);

		getPersonMergeService().processPersonMergeInfo(info, deprecatedVPID, survivingVPID);
	}


	public void processExternalPersonMove(gov.va.med.esr.service.external.person.PersonMoveInfo moveInfo) throws ServiceException {

		if (logger.isInfoEnabled())
            logger.info("ESR received an external person move event: "
                    + moveInfo);

        //CCR 13413 - do not process move events for non vista facility IDs
        //otherwise ES is removing EE data and trying to query the non-vista site for move events for 200IP,200DOD,200BRLS, etc
        VAFacility facility = lookupService.getVaFacilityByCode(moveInfo.getStationNumber());

        if (facility.getType().getIsMedicalTreating()) {
	        /* Current the ESR implementation of HealtheVet "move" is to treat as an "unmerge" */
			PersonUnmergeInfo info = new PersonUnmergeInfo();

			PersonIdEntityKey fromPersonIdEntityKey = CommonEntityKeyFactory.createPersonIdEntityKey(doGetPerson(CommonEntityKeyFactory.createVPIDEntityKey(moveInfo.getFromVpid())).getPersonEntityKey().getKeyValueAsString());
			PersonIdEntityKey toPersonIdEntityKey = CommonEntityKeyFactory.createPersonIdEntityKey(doGetPerson(CommonEntityKeyFactory.createVPIDEntityKey(moveInfo.getToVpid())).getPersonEntityKey().getKeyValueAsString());
			if(fromPersonIdEntityKey == null)
				throw new ServiceException("Unable to retrieve Person for fromVPID: " + moveInfo.getFromVpid());
			if(toPersonIdEntityKey == null)
				throw new ServiceException("Unable to retrieve Person for toVPID: " + moveInfo.getToVpid());

			info.setFromPersonEntityKey(fromPersonIdEntityKey);
			info.setToPersonEntityKey(toPersonIdEntityKey);
			info.setDfn(moveInfo.getDfn());
			info.setStationNumber(moveInfo.getStationNumber());

			getPersonUnmergeService().processPersonUnmergeInfo(info);
        }
        else {
        	if (logger.isInfoEnabled())
                logger.info("Ignoring IdM move event for non-medical treating facility: "
                        + moveInfo);
		}
	}


	public void processExternalPersonPrimaryview(gov.va.med.esr.service.external.person.PersonPrimaryViewInfo primaryviewInfo) throws ServiceException {
        if (logger.isInfoEnabled())
            logger.info("ESR received an external person primary view event: "
                    + primaryviewInfo);
        VPIDEntityKey key = CommonEntityKeyFactory.createVPIDEntityKey(primaryviewInfo.getVpid());
		PersonIdEntityKey personkey = CommonEntityKeyFactory.createPersonIdEntityKey(doGetPerson(key).getPersonEntityKey().getKeyValueAsString());

        // Get the traits for the VPID, which contains the date of death data
        PersonIdentityTraits traits = null;
        try
        {
         	//CCR11593, use psDelegate service to utilize the cache to prevent multiple calls to 1305
        	traits = this.getPsDelegateService().getIdentityTraits(key);

        	if (logger.isDebugEnabled())
                logger.debug("IdM Web Service returned: " + traits);
        }
        catch (TimeoutException ex)
        {
            throw new TimeoutServiceException(ex.getMessage(), ex);
        }
        catch (Exception e)
        {

            // Throw a Service Exception
            throw new ServiceException("Unable to get IdentityTraits from PSIM for VPID: " + key.getKeyValueAsString(), e);
        }

        if(traits != null && traits.getDateOfDeathText() != null){

        	//CCR 11536
        	Date dod = null;
        	try
        	{
        		dod = new SimpleDateFormat("yyyyMMdd").parse(traits.getDateOfDeathText());
        	} catch (ParseException pe)
        	{
           		//create work item, log error
    			String errMsg = "Error in Person Notification for Primary View Update process - NCA Date of Death from IdM is not valid, the correct format is yyyyMMdd, but the value retrieved is " + traits.getDateOfDeathText() + ", vpid= " + key.getKeyValueAsString();
    			createWorkloadCase(FunctionalGroup.DQ.getCode(),
    					WkfCaseType.CODE_DEMOGRAPHICS.getCode(), null,
    					errMsg, personkey);

    			logger.error(errMsg, pe);
    			return;
    			//throw new ServiceException(errMsg, pe);
        	}

    		if (logger.isDebugEnabled())
    			logger.debug("IdM Web Service returned the traits and dod is send for processing " + dod );


    	    // CCR11594 -- Get the person for the VPID without calling idmWebService,
            // the person is used to pass into DemographicService, to avoid having to call idmWebService for a second time.
    		// Note we get traits first, then call doGetPerson instead of getPerson directly, as date of death is not copied
    		// in person.getTraits/setTraits

            Person person = null;
            try
            {
            	person = doGetPerson(personkey, traits, false);
            	//CCR12269 - create our own stub record if searching an unknown person with good known vpid
            	if (person == null) {
            		this.verifyAddStubRecord(traits.getVpid(), false);
            		personkey = getPersonIdByVPID(traits.getVpid());
            		person = doGetPerson(personkey, traits, false);
            	}
            }
            catch (TimeoutException ex)
            {
                throw new TimeoutServiceException(ex.getMessage(), ex);
            }
            catch (Exception e)
            {
                // Throw a Service Exception
                throw new ServiceException("Unable to get person for VPID: " + key.getKeyValueAsString(), e);
            }

    		getDemographicService().processDateOfDeathFromIdm(person, dod);
		}
		else{
			//
			if (logger.isDebugEnabled())
                logger.debug("Date of death is null: IDM service returns null" );
		}

	}


	/**
     * @see gov.va.med.esr.service.PersonService#processDataSyncFromHECLegacy(gov.va.med.esr.common.model.person.Person)
     */

	public Person processDataSyncFromHECLegacy(Person person) throws ServiceException {
        Validate.notNull( person, "A veteran must not be null");
        //Person onFile = getPerson((PersonEntityKey)person.getEntityKey());
        // I used String literal since this method will not be used after Activation Phase 1.
        // The change below is to ensure that data sync caches person (for later
        // use as pristine person) prior to calling assessEEImpact.
        Person onFile = ((PersonService)this.getComponent("cachedPersonService")).getPerson((PersonEntityKey)person.getEntityKey());

        /* This block should be investigated to see if it could be moved into the
         * MergeRuleServiceImpl.mergePerson(...) method that is shared across various
         * use cases.  For now, to be safe and isolate for DataSync, it will only be applied here.
         *
         * The current thinking is that there is some business reasons why it should NOT
         * be added to the universal mergePerson method below, but the exact reason needs
         * to be researched.
         */
        // BEGIN
        onFile.setVeteran(person.getVeteran());
        // END

        boolean fromUI = true; // since only syncing UI changed items from HECLegacy

        // Merge data.
        Person merged = this.getMergeRuleService().mergePerson(person, onFile, fromUI);

	 	// general case, perform E&E calculations
	    Person calculated = this.getEligibilityEnrollmentService().assessEEImpact(merged, false);
	    this.getEventRuleService().handleCommonPostEvents(calculated, fromUI);
	    this.getRegistryService().linkPersonRegistries(calculated, onFile, fromUI);
	    return save(calculated);
    }

    /**
     * A method to process appointment data on a person. The person's
     * enrollment status and appointment data meet criteria for
     * conversion process.
     */

	public AppointmentConversionResult processAppointmentConversion(PersonEntityKey key) throws ServiceException {
        Validate.notNull( key, "A veteran must not be null");

    	Person person = getPerson(key);
        Validate.notNull( person.getEnrollmentDetermination(), "A veteran's enrollment record must not be null");

        AppointmentConversionResult result = new AppointmentConversionResult();
    	result.incrementCount(person.getEnrollmentDetermination());

    	person.setAppointmentRequestDate(null);
    	person.setAppointmentRequestResponse(null);

    	// Trigger the ORUZ11 for this person
    	this.getEventRuleService().manageMessageEvents(person);

    	// Persist the person
    	save(person);

    	return result;
	}


	public EEResult processEEForMigratedPerson(PersonEntityKey key) throws ServiceException {
    	/* TODO: this method should be moved to the EligibilityEnrollmentService so that retrieval of the
    	 * Person populates the cache (since it will be proxied).
    	 */
        Validate.notNull( key, "A veteran must not be null");

        /* won't populate cache since not proxied but is "ok" (for subsequent callers) since Hibernate
         * caches Person and PSDelegateServiceImpl caches identity traits.  Not intuitive though, better
         * to use a proxied service to do this.
         */
        Person onFile = getPerson(key);

        EEResult result = null;
        // make sure this Person really needs to have this performed
        Boolean migratedEE = onFile.getMigratedEE();
        if(Boolean.FALSE.equals(migratedEE)) {
	        boolean fromUI = true; // setting this to true is intended
	        Person calculated = this.getEligibilityEnrollmentService().assessEEImpact(onFile, fromUI, false);
	        this.getEventRuleService().handleCommonPostEvents(calculated, fromUI);
	        this.getRegistryService().linkPersonRegistries(calculated, onFile, fromUI);

	        result =  new EEResult(save(calculated));
        } else if(Boolean.TRUE.equals(migratedEE)) {
        	// no need to deal with this record, has already been migrated
        	result = new EEResult(key);
        } else if(migratedEE == null) {
        	throw new ServiceException("Can not (and will not) migrate EE data for a record that was not migrated to begin with");
        }
        return result;
    }

    /**
     * Returns the person lock reason if the person is locked or null if the person is not locked.
     *
     * @param person The person
     * @param identityTraits The identity traits.  If the traits are null, they will be fetched.
     * @return The person lock reason or null if not locked.
     *
     * @throws ServiceException if we try to fetch the traits and can't.
     */
    private PersonLockedReason getPersonLockedReason(Person person, PersonIdentityTraits identityTraits) throws ServiceException
    {
        // Default for not locked
        PersonLockedReason lockReason = null;

        // Fetch the identity traits if we don't have them yet.
        if (identityTraits == null)
        {
            identityTraits = getPsDelegateService().getIdentityTraits(person.getVPIDEntityKey());
        }

        // Lock if there are pending trait updates

        /* Note: since the development servers of PSIM are not talking to MPI, those VPID's that have
         * pending updates will ALWAYS have pending updates.  Since this is not good for development
         * testing (recall ESR reuses a few select VPIDs), we will essentially disable this check for development
         * testing (ie, isDataClean=false).
         */

        if (identityTraits.isHasPendingUpdates() && isDataClean)
        {
            lockReason = PersonLockedReason.PENDING_IDENTITY_TRAIT_UPDATES;
        }
        else
        {
            // Lock if the person is deprecated
            if (identityTraits.isDeprecated())
            {
                lockReason = PersonLockedReason.PERSON_DEPRECATED;
            }
            else
            {
                // Lock if the person has no enrollment determincation record (i.e. a shell record)
                if (person.getEnrollmentDetermination() == null)
                {
                    lockReason = PersonLockedReason.PERSON_NO_ENROLLMENT_DETERMINATION;
                }
                else
                {
                    // Lock if a user is actively working on a person merge
                    // Do this check last since it is an expensive operation (i.e. goes to the DB)
                    //PersonMergeInfo info = getPersonMergeService().getPersonMergeInfo(
                    //        (PersonIdEntityKey) person.getPersonEntityKey());
                    //if ((info != null) && (info.isRecordLocked() != null) && (info.isRecordLocked().booleanValue()))
                	boolean hasActiveMerge = getPersonMergeService().hasActiveMerge((PersonIdEntityKey) person.getPersonEntityKey());
                	if ( hasActiveMerge)
                    {
                        lockReason = PersonLockedReason.PERSON_MERGE_ACTIVE;
                    }
                }
            }
        }

        // Return the lock reason or null if not locked.
        return lockReason;
	}

	/**
	 * Performs a lookup to get VPID using a PersonId
	 *
	 * @param personId
	 * @return
	 * @throws ServiceException
	 */

	public VPIDEntityKey getVPIDByPersonId(PersonIdEntityKey personId)
			throws ServiceException {
		if (personId == null)
			return null;

		VPIDEntityKey vpid = null;
		try {
			vpid = personDAO.getVPIDByPersonId(personId);
		}
		catch (DAOException ex) {
			throw new ServiceException("Failed to getVPID by PersonId", ex);
		}
		return vpid;
	}

	/**
	 * Performs a lookup to get PersonId using a VPID
	 *
	 * @param vpid
	 * @return
	 * @throws ServiceException
	 */

	public PersonIdEntityKey getPersonIdByVPID(VPIDEntityKey vpid)
			throws ServiceException {
		PersonIdEntityKey personId = null;
		if (vpid == null)
			return null;

		try {
			personId = personDAO.getPersonIdByVPID(vpid);
		}
		catch (DAOException ex) {
			throw new ServiceException("Failed to getVPID by PersonId", ex);
		}
		return personId;
	}

  /**
   * For Add a Person, add a person stub into the database with a veteran indicator
   * @param vpid
   * @return
   * @throws ServiceException
   */
	//CCR 12789 - added for anonymous and level 1 because the veteran indicator can not default to true any more
	public PersonEntityKey addPersonStub(VPIDEntityKey vpid, Boolean isVeteran, Character voaInd) throws ServiceException {
		PersonIdEntityKey personKey = null;
		try {
		    // check to see if person already exists
            //CCR 12192: ESR creating person stub record upon addPerson/addCorrelation
            //return key if it already exists instead of throwing serviceException
            personKey = getPersonIdByVPID(vpid);
            if (personKey != null) {
                return personKey;
            }

            Person p = new Person();
            p.setVPIDValue(((VPIDEntityKeyImpl)vpid).getLongVPID());
            p.setSensitiveRecord(Boolean.FALSE);
            p.setVeteran(isVeteran);
            p.setVOAIndicator(voaInd);
            //CCR 12192: constraint on personVPID has been removed for ESR 3.12
            //in association with ESR creating stub records in ADR for addPerson
            //it is no longer necessary to set this field when creating stub record
            //p.setVPID(personDAO.getPersonVPIDByVpidValue(vpid));

            // Save the person stub
            return (PersonEntityKey) personDAO.insertObject(p);
		} catch (DAOException ex) {
			if (ex.getCause() instanceof DataIntegrityViolationException) {
				/* Simulating the dups for local testing
				String longVPID = ((VPIDEntityKeyImpl)vpid).getLongVPID();
				VPIDEntityKey key = CommonEntityKeyFactory.createVPIDEntityKey(longVPID);
				*/
	            personKey = getPersonIdByVPID(vpid);
	            return personKey;
			} else {
				throw new ServiceException("Failed to create Person stub for vpid " + vpid.getKeyValueAsString(), ex);
			}
		}
	}

	 /**
	   * For Add a Person, add a person stub into the database with veteran indicator = true
	   * @param vpid
	   * @return
	   * @throws ServiceException
	   */
	public PersonEntityKey addPersonStub(VPIDEntityKey vpid) throws ServiceException {
		return addPersonStub(vpid, Boolean.TRUE, null);

	}

	 private void createWorkloadCase(String groupType, String caseType, String issueType,
	         String description, PersonEntityKey key) throws ServiceException {
	      Validate.notNull(groupType, "A Group type must not be null");
	      Validate.notNull(caseType, "A case type must not be null");

	      // Create a regular case if message info is not available
	       WorkflowCaseInfo caseInfo = new WorkflowCaseInfo();

	       caseInfo.setPersonEntityKey(key);

	      try {
	         caseInfo.setErrorMessage(description);
	         caseInfo.setGroupType(this.getLookupService()
	               .getFunctionalGroupByCode(groupType));
	         caseInfo.setCaseType(this.getLookupService().getWkfCaseTypeByCode(caseType));
	         if( StringUtils.isNotEmpty(issueType) ) {
	            caseInfo.setIssueType(this.getLookupService()
	                  .getWkfIssueTypeByCode(issueType));
	         }
	         getWorkflowService().autoCreateCase(caseInfo);

	      }
	      catch( Exception e ) {
	         if( this.logger.isDebugEnabled() ) {
	            logger.debug("Case description: " + description);
	            logger.debug("Case type: " + caseType);
	            logger.debug("Failed to create case due to ", e);
	         }
	         throw new ServiceException ("Failed to create a case", e);
	      }
	   }

	  public void updateVOAIndicator (Person person) throws ServiceException
	  {
		  if (person == null)
			  return;

		  try {
			  this.getPersonDAO().updateVOAIndicator (person);
		  } catch (DAOException dex) {
			  throw new ServiceException(dex);
		  }
	  }

	/*
	 * Helper method
	 * Convert the PersonIdentityTraits to Person object
	 */

    private Person convertTraitsToPerson(PersonIdentityTraits source)
    	throws ServiceException
	{
    	if (source == null)
	        return null;

    	Person target = new Person(source.getVpid());

	    // name
	    target.addName(source.getLegalName());

	    // dob
	    BirthRecord dob = source.getBirthRecord();
	    MultipleBirthIndicator isMultipleBirth = source.getMultiBirth();
        if (isMultipleBirth != null) {
        	dob.setMultipleBirth(new Boolean(isMultipleBirth.getValue()));
        }
        target.setBirthRecord(dob);

	    // Gender
	    target.setGender(source.getGender());
	    //WI 305033 - Backout Sigi not ready yet on MVI side
	    //target.setSelfIdentifiedGenderIdentity(source.getSigi());

	    // SSN
	    target.addSsn(source.getSsn());

	    // CCR 10471 ADD A PERSON, per requirement, "search person 1305" will not return address

	    // IdM match type:
	    target.setIdmMatchType(source.getIdmMatchType());

	    return target;
	}


	/*
	 * TODO CCR 10471: Convert the person to Traits
	 * Helper method
	 * Convert the Person object to PersonIdentityTraits
	 */

    private IdmServiceVO convertPersonToIdmServiceVO(Person source)
    	throws ServiceException
	{
    	IdmServiceVO idmServiceVO = new IdmServiceVO();

    	idmServiceVO.setNames(source.getNames());
        SSN ssnOfficial = source.getOfficialSsn();
        if( ssnOfficial != null ) {
        	idmServiceVO.setSsn(ssnOfficial);
        }
//        else {
//          Set ssnOther = getOtherSsns();
//          if( ssnOther != null && !ssnOther.isEmpty() )
//             traits.setSsn((SSN)ssnOther.iterator().next());
//        }
        idmServiceVO.setGender(source.getGender());
        //idmServiceVO.setSigi(source.getSelfIdentifiedGenderIdentity());
        idmServiceVO.setBirthRecord(source.getBirthRecord());
        idmServiceVO.setDeathEvent(false);

        //CCR 13856, set death record, to be accessed by execute1302

        if (source.getDeathRecord() != null) {
	        idmServiceVO.setDeathRecord(source.getDeathRecord());

	        //should almost always be populated from result person
	        if (idmServiceVO.getDeathRecord().getDeathReportDate() == null)
	        	idmServiceVO.getDeathRecord().setDeathReportDate(new Date());


	        if (idmServiceVO.getDeathRecord().getDeathDate() == null && idmServiceVO.getDeathRecord().getDataSource() == null) {
	        	if (idmServiceVO.getDeathRecord().getLazarusDate() == null)
	        		idmServiceVO.getDeathRecord().setLazarusDate(new ImpreciseDate(new Date()));

	        }
        }


        idmServiceVO.setVpid(source.getVPIDEntityKey());

        String patientType = determinePatientType(source);
        // required field Patient Type
        idmServiceVO.setPatientType(patientType);

        //  Add Preferred facility  call to IDM requires that we also send veteran indicator and
        // service connected fields.
        idmServiceVO.setVeteran(Boolean.TRUE.equals(source.isVeteran()));

        // Since e&e has not been determined at the time we call Add Preferred facility correlation,
        // set ServiceConnected to default 'N'
        idmServiceVO.setServiceConnected(false);

		// since the incoming person is new, the mostRecentPreferredFacility would not have been calculated previously
		// just get the only one allowed for ESR user input
		PreferredFacility incomingPF = null;


		if (source.getMostRecentPreferredFacility() != null) {
			incomingPF = new PreferredFacility();
			incomingPF.setFacility(source.getMostRecentPreferredFacility());
		}


		if (incomingPF == null && ! source.getPreferredFacilities().isEmpty()) {
			Iterator iter = source.getPreferredFacilities().iterator();
			incomingPF = (PreferredFacility)iter.next();
		}

		if (incomingPF != null && incomingPF.getFacility()!= null) {
			// only use the primary facility (sending site) when working with idm
			idmServiceVO.setPreferredFacilty(getParentSite(incomingPF.getFacility()));
		}


		String mothersMaidenName = source.getMothersMaidenName();
		if ( mothersMaidenName != null && mothersMaidenName.length() > 0 ) {
			//max len 35 on MMN, but vista can send up to 50 with delims
			mothersMaidenName = mothersMaidenName.replaceAll("~", "");
			if (mothersMaidenName.length() > 35) {
				mothersMaidenName = mothersMaidenName.substring(0, 35);
			}
			idmServiceVO.setMothersMaidenName(mothersMaidenName);
		}

		//CCR11402: added permanent address, including bad address indicator and home phone number
		idmServiceVO.setAddresses(source.getAddresses());
		idmServiceVO.setPhones(source.getPhones());

        return idmServiceVO;
	}


    /**
     * Determine the Patient Type based on the following rule:
     * Initially if eligibility has not yet been determined, we propose the following simplified logic.  If the person is a Veteran, set patient type to NSC VETERAN, otherwise, set patient type to NON-VETERAN (Other).  It would subsequently be updated to the specific patient type by a Z11 message once eligibility is determined.

		If the Veteran Indicator (ZEL-8) = Y,   then  Patient Type = NSC VETERAN
		Else
		Patient Type = NON-VETERAN (OTHER)

		If the Primary Eligibility field is available at the time of the request to MVI, then we are proposing to use the same logic as VistA.  VistA assigns patient type depending on whether the person is a veteran or not, and then service connected or not.  Non-Veterans can be assigned ALLIED VETERAN, COLLATERAL, EMPLOYEE, TRICARE, OTHER depending if these were selected when entering eligibility information.  Active Duty and Military Retiree are determined from the period of service information.  The specific logic is as follows:

		If the Veteran Indicator (ZEL-8) = Y,
		     If Service Connected (ZSP-2) = Y,  then  Patient Type = SC VETERAN
		    If Service Connected (ZSP-2) = N, then  Patient Type = NSC VETERAN

		If the Veteran Indicator (ZEL-8) = N
		    If the Primary Eligibility Code (ZEL-2) = ALLIED VETERAN, then Patient Type = ALLIED VETERAN
		    If the Primary Eligibility Code (ZEL-2) = COLLATERAL OF VET., then Patient Type = COLLATERAL
		    If the Primary Eligibility Code (ZEL-2) = EMPLOYEE, then Patient Type = EMPLOYEE
		    If the Primary Eligibility Code (ZEL-2) = TRICARE, then Patient Type = TRICARE
		    If the Primary Eligibility Code (ZEL-2) = CHAMPVA, or OTHER FEDERAL AGENCY, or SHARING AGREEMENT
		         If Period of Service (ZSP-4) = AIR FORCE   ACTIVE DUTY, or ARMY   ACTIVE DUTY, or COAST GUARD   ACTIVE
		            DUTY, or NAVY, MARINE   ACTIVE DUTY, or OPERATION DESERT SHIELD, then Patient Type = ACTIVE DUTY
		         If Period of Service (ZSP-4) = RETIRED, UNIFORMED FORCES, then Patient Type = MILITARY RETIREE
		    Otherwise, Patient Type = NON-VETERAN (OTHER)

     *
     * @param person
     * @return
     */
    protected String determinePatientType(Person person) {
    	String patientType = null;
    	String primaryEligibilityCode = getPrimaryEligibilityCode(person);

    	if (primaryEligibilityCode == null) {
    		// if eligibility has not yet been determined
    		patientType = Boolean.TRUE.equals(person.isVeteran()) ? NSC_VETERAN_CODE : NON_VETERAN_CODE;
    	} else { //Primary Eligibility field is available
    		if (Boolean.TRUE.equals(person.isVeteran())) {
    			// If the Veteran Indicator (ZEL-8) = Y
	            ServiceConnectionAward scAward = person.getServiceConnectionAward();
	    		if (scAward != null && Boolean.TRUE.equals(scAward.getServiceConnectedIndicator())) {
	    			// If Service Connected (ZSP-2) = Y,  then  Patient Type = SC VETERAN
	    			patientType = SC_VETERAN_CODE;
	    		} else {
	    		    // If Service Connected (ZSP-2) = N, then  Patient Type = NSC VETERAN
	    			patientType = NSC_VETERAN_CODE;
	    		}
    		} else {
    			// If the Veteran Indicator (ZEL-8) = N
    	   		if (primaryEligibilityCode.equals(EligibilityType.ALLIED_VETERAN.getCode())) {
    	   			patientType = ALLIED_VETERAN;
    	   		} else if (primaryEligibilityCode.equals(EligibilityType.COLLATERAL_OF_VETERAN.getCode())) {
    	   			patientType = COLLATERAL;
    	   		} else if (primaryEligibilityCode.equals(EligibilityType.EMPLOYEE.getCode())) {
    	   			patientType = EMPLOYEE;
    	   		} else if (primaryEligibilityCode.equals(EligibilityType.TRICARE_CHAMPUS.getCode())) {
    	   			patientType = TRICARE;
    	   		} else if (primaryEligibilityCode.equals(EligibilityType.CHAMPVA.getCode()) ||
    	   				primaryEligibilityCode.equals(EligibilityType.OTHER_FEDERAL_AGENCY.getCode()) ||
    	   				primaryEligibilityCode.equals(EligibilityType.SHARING_AGREEMENT.getCode())) {
    	   			// check period of service
    	   			String periodOfService = getCurrentPeriodOfService(person);
    	   			if (ServicePeriod.CODE_AIR_FORCE.getCode().equals(periodOfService) ||
	    	   			ServicePeriod.CODE_ARMY.getCode().equals(periodOfService) ||
	    	   			ServicePeriod.CODE_COAST_GUARD.getCode().equals(periodOfService) ||
	    	   			ServicePeriod.CODE_NAVY.getCode().equals(periodOfService) ||
	    	   			ServicePeriod.CODE_OPERATION_DESERT_SHIELD.getCode().equals(periodOfService))
    	   			{
    	   				patientType = ACTIVE_DUTY;
    	   			} else if (ServicePeriod.CODE_RETIRED_UNIFORMED_FORCES_CODE.getCode().equals(periodOfService)) {
     	   				patientType = MILITARY_RETIREE;
    	   			}
    	   		} else {
    	   			// Otherwise, Patient Type = NON-VETERAN (OTHER)
    	   			patientType = NON_VETERAN_CODE;
    	   		}
    		}
     	}

    	return patientType;
    }

	private String getCurrentPeriodOfService(Person person) {
		ServicePeriod period = getHelperService().getCurrentServicePeriod(person.getMilitaryService());
		return period != null ? period.getCode(): null;
	}

    private String getPrimaryEligibilityCode(Person person) {
    	String primaryEligibilityCode = null;

        EnrollmentDetermination enrollment = person.getEnrollmentDetermination();
        if (enrollment != null) {
            Eligibility primary = enrollment.getPrimaryEligibility();
            if (primary != null) {
                EligibilityType type = primary.getType();
                primaryEligibilityCode = (type != null) ? type.getCode() : null;
            }
        }
         return primaryEligibilityCode;
    }

    /**
	 * @return Returns the isDataClean.
	 */
	public boolean isDataClean() {
		return isDataClean;
	}

	/**
	 * @param isDataClean The isDataClean to set.
	 */
	public void setIsDataClean(boolean isDataClean) {
		this.isDataClean = isDataClean;
	}

	/**
	 * @return Returns the isUpdateIdentityTraits.
	 */
	public boolean isUpdateIdentityTraits() {
		return isUpdateIdentityTraits;
	}

	/**
	 * @param isUpdateIdentityTraits The isUpdateIdentityTraits to set.
	 */
	public void setIsUpdateIdentityTraits(boolean isUpdateIdentityTraits) {
		this.isUpdateIdentityTraits = isUpdateIdentityTraits;
	}

	/**
	 * @return Returns the timestampManager.
	 */
	public TransactionTimestampManager getTimestampManager() {
		return timestampManager;
	}

	/**
	 * @param timestampManager The timestampManager to set.
	 */
	public void setTimestampManager(TransactionTimestampManager timestampManager) {
		this.timestampManager = timestampManager;
	}

	/**
     * @return Returns the registryService.
     */
    public RegistryService getRegistryService()
    {
    	if (this.registryService == null)
            this.registryService = (RegistryService) getApplicationContext()
                .getBean(this.getRegistryServiceName(), RegistryService.class);
        return this.registryService;
    }

    /**
     * @return Returns the registryServiceName.
     */
    public String getRegistryServiceName() {

        return registryServiceName;
    }

    /**
     * @param registryServiceName The registryServiceName to set.
     */
    public void setRegistryServiceName(String registryServiceName) {
        this.registryServiceName = registryServiceName;
    }

	public IdmWebServiceDelegate getIdmServiceDelegate() {
		return idmServiceDelegate;
	}

	public void setIdmServiceDelegate(IdmWebServiceDelegate idmServiceDelegate) {
		this.idmServiceDelegate = idmServiceDelegate;
	}

	public PersonHelperService getHelperService() {
        return this.helperService;
    }

    public void setHelperService(PersonHelperService service) {
        this.helperService = service;
    }



    private DemographicService getDemographicService()
    {
    	if (demographicService == null)
    	{
    		ApplicationContext context = getApplicationContext();
    		demographicService = (DemographicService) context.getBean("demographicService");
    	}

    	return demographicService;
    }

	public WorkflowService getWorkflowService() {

    	if (workflowService == null)
    	{
    		ApplicationContext context = getApplicationContext();
    		workflowService = (WorkflowService) context.getBean("workflowService");
    	}

		return workflowService;
	}

	//CCR12269 - searches for newly registered patients are failing due to no person stub
	//record found. This utility method is called from searches by vpid when the returned
	//person is null. We need to add our own stub record for known good vpid, when esr person is unknown

	private void verifyAddStubRecord(VPIDEntityKey vpid, boolean validateVpid) throws ServiceException {

		if (vpid == null)
			throw new ServiceException ("Cannot create person stub record for NULL VPID");

		//if the caller didn't explicitly pull the vpid from the traits, check if it's valid
		if (validateVpid) {
			PersonIdentityTraits traits = psDelegateService.getIdentityTraits(vpid);
			if (traits == null) {
				throw new ServiceException ("Failed to create person stub record for new enrollment. Invalid VPID:" + vpid.getVPID());
			}
		}

		PersonEntityKey personKey = this.addPersonStub(vpid);

		if (personKey == null)
			throw new ServiceException("Failed to create person stub record for VPID:" + vpid.getVPID());

	}

	//TODO getIDs for VHIC

}