/*******************************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ******************************************************************************/
// Package
package gov.va.med.esr.common.rule.service.impl;

// Java classes
import gov.va.med.esr.common.infra.ImpreciseDate;
import gov.va.med.esr.common.model.SiteYear;
import gov.va.med.esr.common.model.ee.Activation;
import gov.va.med.esr.common.model.ee.Application;
import gov.va.med.esr.common.model.ee.CDCondition;
import gov.va.med.esr.common.model.ee.CDDescriptor;
import gov.va.med.esr.common.model.ee.CDDiagnosis;
import gov.va.med.esr.common.model.ee.CDProcedure;
import gov.va.med.esr.common.model.ee.CancelDecline;
import gov.va.med.esr.common.model.ee.CatastrophicDisability;
import gov.va.med.esr.common.model.ee.CombatEpisode;
import gov.va.med.esr.common.model.ee.CombatService;
import gov.va.med.esr.common.model.ee.ConflictExperience;
import gov.va.med.esr.common.model.ee.Deployment;
import gov.va.med.esr.common.model.ee.EGTProcessStatistic;
import gov.va.med.esr.common.model.ee.EGTSetting;
import gov.va.med.esr.common.model.ee.EGTSiteStatistic;
import gov.va.med.esr.common.model.ee.Eligibility;
import gov.va.med.esr.common.model.ee.EligibilityVerification;
import gov.va.med.esr.common.model.ee.EnrollmentDetermination;
import gov.va.med.esr.common.model.ee.EnrollmentOverride;
import gov.va.med.esr.common.model.ee.FeeBasis;
import gov.va.med.esr.common.model.ee.HealthBenefitPlan;
import gov.va.med.esr.common.model.ee.HealthBenefitProfile;
import gov.va.med.esr.common.model.ee.IncompetenceRuling;
import gov.va.med.esr.common.model.ee.IneligibilityFactor;
import gov.va.med.esr.common.model.ee.MedalOfHonor;
import gov.va.med.esr.common.model.ee.MedicaidFactor;
import gov.va.med.esr.common.model.ee.MilitaryService;
import gov.va.med.esr.common.model.ee.MilitaryServiceEpisode;
import gov.va.med.esr.common.model.ee.MilitaryServiceSiteRecord;
import gov.va.med.esr.common.model.ee.MilitarySexualTrauma;
import gov.va.med.esr.common.model.ee.MonetaryBenefit;
import gov.va.med.esr.common.model.ee.MonetaryBenefitAward;
import gov.va.med.esr.common.model.ee.Pension;
import gov.va.med.esr.common.model.person.Name;
import gov.va.med.esr.common.model.ee.NoseThroatRadium;
import gov.va.med.esr.common.model.ee.POWEpisode;
import gov.va.med.esr.common.model.ee.PrisonerOfWar;
import gov.va.med.esr.common.model.ee.PurpleHeart;
import gov.va.med.esr.common.model.ee.PurpleHeartDocument;
import gov.va.med.esr.common.model.ee.RatedDisability;
import gov.va.med.esr.common.model.ee.ReceivedEligibility;
import gov.va.med.esr.common.model.ee.ReceivedEnrollment;
import gov.va.med.esr.common.model.ee.SHAD;
import gov.va.med.esr.common.model.ee.SHADDocument;
import gov.va.med.esr.common.model.ee.ServiceConnectionAward;
import gov.va.med.esr.common.model.ee.SpecialFactor;
import gov.va.med.esr.common.model.financials.Asset;
import gov.va.med.esr.common.model.financials.BeneficiaryTravel;
import gov.va.med.esr.common.model.financials.Debt;
import gov.va.med.esr.common.model.financials.DependentFinancials;
import gov.va.med.esr.common.model.financials.Expense;
import gov.va.med.esr.common.model.financials.FinancialInformation;
import gov.va.med.esr.common.model.financials.FinancialStatement;
import gov.va.med.esr.common.model.financials.Hardship;
import gov.va.med.esr.common.model.financials.Income;
import gov.va.med.esr.common.model.financials.IncomeTest;
import gov.va.med.esr.common.model.financials.IncomeTestStatus;
import gov.va.med.esr.common.model.financials.PatientVisitSummary;
import gov.va.med.esr.common.model.financials.SpouseFinancials;
import gov.va.med.esr.common.model.insurance.InsurancePolicy;
import gov.va.med.esr.common.model.lookup.AssetType;
import gov.va.med.esr.common.model.lookup.CampLejeuneVerificationType;
import gov.va.med.esr.common.model.lookup.EligibilityType;
import gov.va.med.esr.common.model.lookup.ExpenseType;
import gov.va.med.esr.common.model.lookup.IncomeType;
import gov.va.med.esr.common.model.lookup.PensionReasonCode;
import gov.va.med.esr.common.model.lookup.SSAMessage;
import gov.va.med.esr.common.model.lookup.SSAVerificationStatus;
import gov.va.med.esr.common.model.lookup.Relationship;
import gov.va.med.esr.common.model.lookup.SensitivityChangeSource;
import gov.va.med.esr.common.model.party.Address;
import gov.va.med.esr.common.model.party.ConfidentialAddressCategory;
import gov.va.med.esr.common.model.party.Email;
import gov.va.med.esr.common.model.party.Phone;
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.DeathRecord;
import gov.va.med.esr.common.model.person.Dependent;
import gov.va.med.esr.common.model.person.EmergencyResponseIndicator;
import gov.va.med.esr.common.model.person.HealthCareProvider;
import gov.va.med.esr.common.model.person.PatientProviderAssignmentLite;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.PreferredFacility;
import gov.va.med.esr.common.model.person.PreferredLanguage;
import gov.va.med.esr.common.model.person.Relation;
import gov.va.med.esr.common.model.person.SSN;
import gov.va.med.esr.common.model.person.SignatureImage;
import gov.va.med.esr.common.model.person.Spouse;
import gov.va.med.esr.common.model.person.Race;
import gov.va.med.esr.common.model.person.Ethnicity;
import gov.va.med.esr.common.model.person.Employment;
import gov.va.med.esr.common.model.registry.RegistryTrait;
import gov.va.med.esr.common.model.registry.RegistryTraitDetail;
import gov.va.med.esr.common.model.report.ReportParameterSet;
import gov.va.med.esr.common.model.report.ReportSchedule;
import gov.va.med.esr.common.model.report.ReportSetup;
import gov.va.med.esr.common.model.security.ESRUserPrincipalImpl;
import gov.va.med.esr.common.rule.service.MatchRuleService;
import gov.va.med.esr.common.rule.service.MergeRuleService;
import gov.va.med.esr.service.LookupService;
import gov.va.med.esr.service.UnknownLookupCodeException;
import gov.va.med.esr.service.UnknownLookupTypeException;
import gov.va.med.fw.conversion.CopyService;
import gov.va.med.fw.conversion.CopyServiceException;
import gov.va.med.fw.model.AbstractEntity;
import gov.va.med.fw.model.EntityKey;
import gov.va.med.fw.rule.AbstractRuleFlowAwareService;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.util.StringUtils;
import gov.va.med.esr.common.model.ee.CampLejeuneVerification;
import gov.va.med.esr.common.model.ee.CampLejeuneVerificationMethod;

import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.Validate;

/**
 * Provides methods to merge 2 business entities based on sepcific business
 * rules. Project: Common</br> Created on: 4:19:48 PM </br>
 *
 * @author DNS   LEV
 */
public class MergeRuleServiceImpl extends AbstractRuleFlowAwareService
		implements MergeRuleService {

	/**
	 * An instance of serialVersionUID
	 */
	private static final long serialVersionUID = 4783737960515226873L;

	/**
	 * An instance of matchRuleService
	 */
	private MatchRuleService matchRuleService = null;

	/**
	 * An instance of copyService
	 */
	private CopyService copyService = null;

	private LookupService lookupService = null;

	/**
	 * A default constructor
	 */
	public MergeRuleServiceImpl() {
		super();
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeIneligibilityFactor(gov.va.med.esr.common.model.ee.IneligibilityFactor,
	 *      gov.va.med.esr.common.model.ee.IneligibilityFactor)
	 */
	public IneligibilityFactor mergeIneligibilityFactor(
			IneligibilityFactor incoming, Person onFile)
			throws ServiceException {

		Validate.notNull(onFile, "An on file person must not be null");

		IneligibilityFactor updated = incoming;
		if (incoming != null) {
			updated = onFile.getIneligibilityFactor();
			if (updated == null) {
				updated = new IneligibilityFactor();
				onFile.setIneligibilityFactor(updated);
			}
			this.getCopyService().copy(incoming, updated);
		} else {			// incoming is equal NULL in this case
			onFile.setIneligibilityFactor(incoming);
		}
		return updated;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeInsuranceAddress(gov.va.med.esr.common.model.party.Address,
	 *      gov.va.med.esr.common.model.insurance.InsurancePolicy)
	 */
	public InsurancePolicy mergeInsuranceAddress(Address incoming,
			InsurancePolicy onFile) throws ServiceException {

		// A policy does NOT require an address
		Validate.notNull(onFile, "An insurance policy on file must not be null");

		// No rules pertaining to an address for insurance policy so
		// we simply merge data
		Address currentAddress = onFile.getAddress();

		if (incoming == null) {
			// Delete the existing address since address for policy is not
			// required
			onFile.setAddress(null);
		} else if (currentAddress != null) {
			// If found, update the data
			this.getCopyService().copy(incoming, currentAddress);
		} else {
			// Create a new instance to insert to the record on file
			Address newAddress = new Address();
			this.getCopyService().copy(incoming, newAddress);
			onFile.setAddress(newAddress);
		}
		return onFile;
	}

	/**
	 * Merge Insuarnce Phone (PreCertification Phone)
	 *
	 * @param incoming
	 * @param onFile
	 * @return
	 * @throws ServiceException
	 */
	public InsurancePolicy mergeInsurancePreCertificationPhone(Phone incoming,
			InsurancePolicy onFile) throws ServiceException {

		// A policy does NOT require a phone
		Validate.notNull(onFile, "An insurance policy on file must not be null");

		// No rules pertaining to an phone for insurance policy so
		Phone currentPhone = onFile.getPreCertificationPhone();

		if (incoming == null) {
			// Delete the existing phone since phone for policy is not required
			onFile.setPreCertificationPhone(null);
		} else {
			if (currentPhone != null) {
				mergePhone(incoming, currentPhone);
			} else {
				currentPhone = new Phone();
				mergePhone(incoming, currentPhone);
				onFile.setPreCertificationPhone(currentPhone);
			}
		}

		return onFile;
	}

	/**
	 * Merge Insuarnce Phone (Business Phone)
	 *
	 * @param incoming
	 * @param onFile
	 * @return
	 * @throws ServiceException
	 */
	public InsurancePolicy mergeInsuranceBusinessPhone(Phone incoming,
			InsurancePolicy onFile) throws ServiceException {

		// A policy does NOT require a phone
		Validate.notNull(onFile, "An insurance policy on file must not be null");

		// No rules pertaining to an phone for insurance policy so
		Phone currentPhone = onFile.getBusinessPhone();

		if (incoming == null) {
			// Delete the existing phone since phone for policy is not required
			onFile.setBusinessPhone(null);
		} else {
			if (currentPhone != null) {
				mergePhone(incoming, currentPhone);
			} else {
				currentPhone = new Phone();
				mergePhone(incoming, currentPhone);
				onFile.setBusinessPhone(currentPhone);
			}
		}

		return onFile;
	}

	/**
	 * Merge Insurance Fax (Phone)
	 *
	 * @param incoming
	 * @param onFile
	 * @return
	 * @throws ServiceException
	 */
	public InsurancePolicy mergeInsuranceFax(Phone incoming,
			InsurancePolicy onFile) throws ServiceException {

		// A policy does NOT require a fax
		Validate.notNull(onFile, "An insurance policy on file must not be null");

		// No rules to validate Fax or Phone
		Phone currentFax = onFile.getFax();

		if (incoming == null) {
			// Delete the existing fax since fax for policy is not required
			onFile.setFax(null);
		} else {
			if (currentFax != null) {
				mergePhone(incoming, currentFax);
			} else {
				// Create a new instance to insert to the record on file
				currentFax = new Phone();
				mergePhone(incoming, currentFax);
				onFile.setFax(currentFax);
			}
		}

		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeInsurancePolicy(gov.va.med.esr.common.model.insurance.InsurancePolicy,
	 *      gov.va.med.esr.common.model.person.Person)
	 */
	public InsurancePolicy mergeInsurancePolicy(InsurancePolicy incoming,
			Person onFile) throws ServiceException {

		Validate.notNull(incoming, "An insurance policy must not be null");
		Validate.notNull(onFile, "A person on file must not be null");

		Set policies = onFile.getInsurances();

		// If a match is not found, this is a new record so return
		// it so we can insert into a person on file later
		InsurancePolicy match = incoming;

		for (Iterator i = policies.iterator(); i.hasNext();) {

			// Looking for a match. If found, update it with
			// data from an in-coming association
			AbstractEntity obj = (AbstractEntity) i.next();
			if (this.getMatchRuleService()
					.match((AbstractEntity) obj, incoming)) {
				match = (InsurancePolicy) obj;
				this.getCopyService().copy(incoming, match);
				// Merge Address
				mergeInsuranceAddress(incoming.getAddress(), match);

				// Merge PreCertification Phone
				mergeInsurancePreCertificationPhone(
						incoming.getPreCertificationPhone(), match);

				// Merge Buisness Phone
				mergeInsuranceBusinessPhone(incoming.getBusinessPhone(), match);

				// Merge Fax
				mergeInsuranceFax(incoming.getFax(), match);
				break;
			}
		}
		return match;
	}

	/**
	 * Address is excluded as the Business services makes a separate call to
	 * merge address
	 */
	public InsurancePolicy mergeInsurancePolicy(InsurancePolicy incoming,
			InsurancePolicy onFile) throws ServiceException {
		Validate.notNull(incoming,
				"An incoming InsurancePolicy must not be null");
		Validate.notNull(onFile,
				"An incoming on file  InsurancePolicy must not be null");
		this.getCopyService().copy(incoming, onFile);
		mergeInsuranceAddress(incoming.getAddress(), onFile);
		mergeInsurancePreCertificationPhone(
				incoming.getPreCertificationPhone(), onFile);
		mergeInsuranceBusinessPhone(incoming.getBusinessPhone(), onFile);
		mergeInsuranceFax(incoming.getFax(), onFile);
		return onFile;
	}

	/**
	 * Merge address for association
	 *
	 * @param incoming
	 * @param onFile
	 * @return
	 * @throws ServiceException
	 */
	public Association mergeAssociationAddress(Address incoming,
			Association onFile) throws ServiceException {
		if (incoming == null) {
			onFile.setAddress(null);
		} else {
			Address currentAddress = onFile.getAddress();

			if (currentAddress == null) {
				currentAddress = new Address();
				mergeAddress(incoming, currentAddress);
				onFile.setAddress(currentAddress);
			} else {
				mergeAddress(incoming, currentAddress);
			}
		}

		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeAssociation(gov.va.med.esr.common.model.person.Association,
	 *      gov.va.med.esr.common.model.person.Association)
	 */
	public Association mergeAssociation(Association incoming, Person onFile)
			throws ServiceException {

		Validate.notNull(incoming, "An association must not be null");
		Validate.notNull(onFile, "A person on file must not be null");

		Set policies = onFile.getAssociations();

		// If a match is not found, this is a new record so return
		// it so we can insert into a person on file later
		Association match = incoming;

		for (Iterator i = policies.iterator(); i.hasNext();) {

			// Looking for a match. If found, update it with
			// data from an in-coming association
			AbstractEntity obj = (AbstractEntity) i.next();
			if (this.getMatchRuleService()
					.match((AbstractEntity) obj, incoming)) {
				match = (Association) obj;
				mergeAssociation(incoming, match);
				break;
			}
		}
		return match;
	}

	public Association mergeAssociation(Association incoming, Association onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An association must not be null");
		Validate.notNull(onFile, "A person on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		mergeAssociationAddress(incoming.getAddress(), onFile);
		return onFile;
	}

	/**
	 * Merges the demographic information in incoming person to the person on
	 * file. The data merged by this method depends on mapping for the
	 * CopyService (see commons.xml).
	 *
	 * @param incoming
	 *            the incoming person
	 * @param onFile
	 *            the person on file
	 * @return person on file after merging.
	 * @throws ServiceException
	 */
	public Person mergeDemographic(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An incoming person must not be null");
		Validate.notNull(onFile, "A person on file must not be null");
		this.getCopyService().copy(incoming, onFile);

		// Merge Confidential address categories
		Set mergedConfidentialAddressCategories = this.mergeSets(
				incoming.getConfidentialAddressCategories(),
				onFile.getConfidentialAddressCategories());

		// Remove all ConfidentialAddressCategories and add merged set
		onFile.removeAllConfidentialAddressCategories();

		for (Iterator iter = mergedConfidentialAddressCategories.iterator(); iter
				.hasNext();) {
			ConfidentialAddressCategory category = (ConfidentialAddressCategory) iter
					.next();
			onFile.addConfidentialAddressCategory(category.getType());
		}

		// merge new ESR 3.1 VOA fields - CodeCR7423
		this.mergeOtherTraits(incoming, onFile);
		this.mergeEmployment(incoming, onFile);
		// end of CodeCR7423
		this.mergePreferredLanguage(incoming, onFile);

		return onFile;
	}

	public void mergeEmployment(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");
		Employment incomingEM = incoming.getEmployment();
		Employment onFileEM = onFile.getEmployment();

		if (incomingEM != null) {
			if (onFileEM == null) {
				onFileEM = new Employment();
				this.mergeEmployment(incomingEM, onFileEM);
				onFile.setEmployment(onFileEM);
			} else
				this.mergeEmployment(incomingEM, onFileEM);
		} else {
			onFile.setEmployment(null);
		}
	}

    /**
     * Merge the Preferred Language related fields.
     * @param incoming PreferredLanguage containing the changes to apply
     * @param onFile PreferredLanguage containing the last version from the persistence store
     * @return
     * @throws ServiceException If errors encountered accessing supporting services
     */
    public PreferredLanguage mergePreferredLanguage(PreferredLanguage incoming, PreferredLanguage onFile)
            throws ServiceException {
        Validate.notNull(incoming, "An incoming  preferred language not be null");
        Validate.notNull(onFile,"An incoming on file preferred language must not be null");
        this.getCopyService().copy(incoming, onFile);
        return onFile;
    }

    /**
     * Merge the Preferred Language related fields.
     * @param incoming Person containing the changes to apply
     * @param onFile Person containing the last version from the persistence store
     * @throws ServiceException If errors encountered accessing supporting services
     */
    public void mergePreferredLanguage(Person incoming, Person onFile)
            throws ServiceException {
        Validate.notNull(incoming, "An incoming person must not be null ");
        Validate.notNull(onFile, "An onFile person must not be null ");
        PreferredLanguage incomingPL = incoming.getPreferredLanguage();
        PreferredLanguage onFilePL = onFile.getPreferredLanguage();

        if (incomingPL != null) {
            if (onFilePL == null) {
                onFilePL = new PreferredLanguage();
                this.mergePreferredLanguage(incomingPL, onFilePL);
                onFile.setPreferredLanguage(onFilePL);
            } else
                this.mergePreferredLanguage(incomingPL, onFilePL);
        } else {
            onFile.setPreferredLanguage(null);
        }
    }

	public void mergeOtherTraits(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		// race -
		Set races = incoming.getRaces();
		if (races != null && races.size() > 0) {
			Set merged = this.mergeSets(races, onFile.getRaces());
			onFile.removeAllRaces();
			for (Iterator iter = merged.iterator(); iter.hasNext();) {
				Race race = (Race) iter.next();
				onFile.addRace(race);
			}
		} else {
			onFile.removeAllRaces();
		}

		// ethnicity
		Ethnicity incomingEthnicity = incoming.getEthnicity();
		if (incomingEthnicity != null) {
			Ethnicity onFileEthnicity = onFile.getEthnicity();
			if (onFileEthnicity == null) {
				onFileEthnicity = new Ethnicity();
				onFile.setEthnicity(onFileEthnicity);
			}
			this.getCopyService().copy(incomingEthnicity, onFileEthnicity);
		} else {
			onFile.setEthnicity(null);
		}

		// marital status and religion - direct lookup like Gender in
		// IdentityTraits
		onFile.setMaritalStatus(incoming.getMaritalStatus());
		onFile.setReligion(incoming.getReligion());

		// father's and mother's name - design data is through relation
		Relation incomingMother = incoming.getMother();
		Relation onFileMother = onFile.getMother();
		if (incomingMother != null) {
			this.mergeMotherName(incomingMother, onFile);
		} else {
			if (onFileMother != null) {
				onFile.removeRelation(onFileMother);
			}
		}

		Relation incomingFather = incoming.getFather();
		Relation onFileFather = onFile.getFather();
		if (incomingFather != null) {
			this.mergeFatherName(incomingFather, onFile);
		} else {
			if (onFileFather != null) {
				onFile.removeRelation(onFileFather);
			}
		}
	}

	public Person mergeMotherName(Relation incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A Relation must not be null");
		Validate.notNull(onFile, "A Person must not be null");

		Relation onFileMother = onFile.getMother();
		Name incomingMotherName = incoming.getName();
		if (incomingMotherName != null) {
			if (onFileMother == null) {
				onFileMother = new Relation();
				onFileMother.setRelationship(getLookupService()
						.getRelationshipByCode(
								Relationship.CODE_MOTHER.getCode()));
				onFile.setMother(onFileMother);
			}

			Name onFileMotherName = onFileMother.getName();
			if (onFileMotherName == null) {
				onFileMotherName = new Name();
				onFileMother.setName(onFileMotherName);
			}

			this.getCopyService().copy(incomingMotherName, onFileMotherName);
		} else {
			onFile.removeRelation(onFileMother);
		}
		return onFile;
	}

	public Person mergeFatherName(Relation incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A Relation must not be null");
		Validate.notNull(onFile, "A Person must not be null");

		Relation onFileFather = onFile.getFather();
		Name incomingFatherName = incoming.getName();
		if (incomingFatherName != null) {
			if (onFileFather == null) {
				onFileFather = new Relation();
				onFileFather.setRelationship(getLookupService()
						.getRelationshipByCode(
								Relationship.CODE_FATHER.getCode()));
				onFile.setFather(onFileFather);
			}

			Name onFileFatherName = onFileFather.getName();
			if (onFileFatherName == null) {
				onFileFatherName = new Name();
				onFileFather.setName(onFileFatherName);
			}
			this.getCopyService().copy(incomingFatherName, onFileFatherName);
		} else {
			onFile.removeRelation(onFileFather);
		}
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeEmail(gov.va.med.esr.common.model.party.Email,
	 *      gov.va.med.esr.common.model.person.Person)
	 */
	public Email mergeEmail(Email incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An email must not be null");
		Validate.notNull(onFile, "A person on file must not be null");

		Set emails = onFile.getEmails();

		Email match = null;
		Set remove = new HashSet();
		int matchCount = 0;

		for (Iterator i = emails.iterator(); i.hasNext();) {

			// Looking for a match. Must search for multiple matches.
			AbstractEntity obj = (AbstractEntity) i.next();
			if (this.getMatchRuleService()
					.match((AbstractEntity) obj, incoming)) {
				// Increment matching counter
				matchCount++;
				match = (Email) obj;
				// Hold onto the specific type that matched since
				// may need to delete
				remove.add(match);
			}
		}

		// Only allow one match otherwise not considered
		// a match.
		if (matchCount == 1) {
			// Update existing
			this.getCopyService().copy(incoming, match);
		} else {
			// Remove any emails with multiple match
			if (matchCount > 1) {
				Iterator removeIter = remove.iterator();
				while (removeIter.hasNext()) {
					Email emailToRemove = (Email) removeIter.next();
					onFile.removeEmail(emailToRemove);
				}
			}
			// Create new email
			match = new Email();
			this.getCopyService().copy(incoming, match);
			onFile.addEmail(match);
		}

		return match;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergePhone(gov.va.med.esr.common.model.party.Phone,
	 *      gov.va.med.esr.common.model.person.Person)
	 */
	public Phone mergePhone(Phone incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A phone must not be null");
		Validate.notNull(onFile, "A person on file must not be null");

		Set phones = onFile.getPhones();

		Phone match = null;
		Set remove = new HashSet();
		int matchCount = 0;

		for (Iterator i = phones.iterator(); i.hasNext();) {
			// Looking for a match. Must search for multiple matches.
			AbstractEntity obj = (AbstractEntity) i.next();
			if (this.getMatchRuleService()
					.match((AbstractEntity) obj, incoming)) {
				// Increment matching counter
				matchCount++;
				match = (Phone) obj;
				// Hold onto the specific type that matched since
				// may need to delete
				remove.add(match);
			}
		}

		// Only allow one match otherwise not considered
		// a match.
		if (matchCount == 1) {
			// Update existing
			this.getCopyService().copy(incoming, match);
		} else {
			// Remove any phones with multiple match
			if (matchCount > 1) {
				Iterator removeIter = remove.iterator();
				while (removeIter.hasNext()) {
					Phone phoneToRemove = (Phone) removeIter.next();
					onFile.removePhone(phoneToRemove);
				}
			}
			// Create new phone
			match = new Phone();
			this.getCopyService().copy(incoming, match);
			onFile.addPhone(match);
		}

		return match;
	}

	/**
	 * Merge Address
	 */
	public Address mergeAddress(Address incoming, Address onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An incoming address must not be null");
		Validate.notNull(onFile, "An incoming on file address must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeEmail(gov.va.med.esr.common.model.party.Email,
	 *      gov.va.med.esr.common.model.party.Email)
	 */
	public Email mergeEmail(Email incoming, Email onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An email must not be null");
		Validate.notNull(onFile, "An email on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergePhone(gov.va.med.esr.common.model.party.Phone,
	 *      gov.va.med.esr.common.model.party.Phone)
	 */
	public Phone mergePhone(Phone incoming, Phone onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A phone must not be null");
		Validate.notNull(onFile, "A phone on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * Merges the MilitaryServiceSiteRecord objects.
	 *
	 * @param incoming
	 *            the incoming MilitaryServiceSiteRecord
	 * @param onFile
	 *            the MilitaryServiceSiteRecord on file
	 * @throws ServiceException
	 */
	public void mergeMilitaryServiceSiteRecord(
			MilitaryServiceSiteRecord incoming, MilitaryServiceSiteRecord onFile) {
		Validate.notNull(incoming,
				"A MilitaryServiceSiteRecord must not be null");
		Validate.notNull(onFile,
				"A MilitaryServiceSiteRecord on file must not be null");
		try {

			// Copy site record fields except the MSEs and CEs.
			this.getCopyService().copy(incoming, onFile);

		//	Set onFileMSEs = new HashSet(onFile.getMilitaryServiceEpisodes());
			Set<MilitaryServiceEpisode> toAddMSEs = new HashSet<MilitaryServiceEpisode>();
			/*
			for (Iterator iter = onFileMSEs.iterator(); iter.hasNext();) {
				MilitaryServiceEpisode mse = (MilitaryServiceEpisode) iter
						.next();
				if (incoming.getMilitaryServiceEpisodeByEntityKey(mse
						.getEntityKey()) == null) {
					onFile.removeMilitaryServiceEpisode(mse);
				}
			}
*/
			// Add/update MilitaryServiceEpisodes
			for (Iterator iter = incoming.getMilitaryServiceEpisodes()
					.iterator(); iter.hasNext();) {
				MilitaryServiceEpisode incomingEpisode = (MilitaryServiceEpisode) iter
						.next();
				EntityKey key = incomingEpisode.getEntityKey();
				if (key == null) {
					// Newly added MilitaryServiceEpisode, just add it
					MilitaryServiceEpisode mse = new MilitaryServiceEpisode();
					this.mergeMilitaryServiceEpisode(incomingEpisode, mse);
					toAddMSEs.add(mse);
				} else {
					// Updated MilitaryServiceEpisode, merge it
					MilitaryServiceEpisode onFileEpisode = onFile.getMilitaryServiceEpisodeByEntityKey(key);

					if (onFileEpisode == null) {
						// In case of merge when MSE is not found on file for selected key then add it
						onFileEpisode = new MilitaryServiceEpisode();
					}

					this.mergeMilitaryServiceEpisode(incomingEpisode, onFileEpisode);
					toAddMSEs.add(onFileEpisode);
				}
			}
			onFile.removeAllMilitaryServiceEpisodes();
			if (!toAddMSEs.isEmpty()) {
				onFile.addAllMilitaryServiceEpisodes(toAddMSEs);
			}
		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge an MilitaryServiceEpisode", ex);
			throw new RuntimeException(
					"Failed to merge an MilitaryServiceEpisode", ex);
		}

		try {
			// Remove deleted ConflictExperiences
			Set onFileCEs = new HashSet(onFile.getConflictExperiences());
			for (Iterator iter = onFileCEs.iterator(); iter.hasNext();) {
				ConflictExperience ce = (ConflictExperience) iter.next();
				if (incoming
						.getConflictExperienceByEntityKey(ce.getEntityKey()) == null) {
					onFile.removeConflictExperience(ce);
				}
			}

			// Add/update ConflictExperiences
			for (Iterator iter = incoming.getConflictExperiences().iterator(); iter
					.hasNext();) {
				ConflictExperience incomingCE = (ConflictExperience) iter
						.next();
				EntityKey key = incomingCE.getEntityKey();
				if (key == null) {
					// Newly added ConflictExperience, just add it
					ConflictExperience ce = new ConflictExperience();
					this.mergeConflictExperience(incomingCE, ce);
					onFile.addConflictExperience(ce);
				} else {
					// Updated ConflictExperience, merge it
					ConflictExperience onFileCE = onFile
							.getConflictExperienceByEntityKey(key);
					this.mergeConflictExperience(incomingCE, onFileCE);
				}
			}
		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge a Combat Experience", ex);
			throw new RuntimeException("Failed to merge a Combat Experience",
					ex);
		}
	}

	/**
	 * Merges the MilitaryServiceSiteRecord objects WITHOUT DELETES.
	 *
	 * @param incoming
	 *            the incoming MilitaryServiceSiteRecord
	 * @param onFile
	 *            the MilitaryServiceSiteRecord on file
	 * @throws ServiceException
	 */
	private void mergeMilitaryServiceSiteRecordNoDeletes(
			MilitaryServiceSiteRecord incoming, MilitaryServiceSiteRecord onFile) {
		Validate.notNull(incoming,
				"A MilitaryServiceSiteRecord must not be null");
		Validate.notNull(onFile,
				"A MilitaryServiceSiteRecord on file must not be null");
		// / NOTICE NO DELETIONS SHOULD OCCUR ANYWHERE IN THIS METHOD
		// / NOTICE NO DELETIONS SHOULD OCCUR ANYWHERE IN THIS METHOD
		// / NOTICE NO DELETIONS SHOULD OCCUR ANYWHERE IN THIS METHOD
		try {
			// Copy site record fields except the MSEs and CEs.
			this.getCopyService().copy(incoming, onFile);

			// Add/update MilitaryServiceEpisodes
			for (Iterator iter = incoming.getMilitaryServiceEpisodes()
					.iterator(); iter.hasNext();) {
				MilitaryServiceEpisode incomingEpisode = (MilitaryServiceEpisode) iter
						.next();
				EntityKey key = incomingEpisode.getEntityKey();
				MilitaryServiceEpisode mse = null;
				if (key == null) {
					// There is a chance that object has same values in business
					// attributes
					Set episodes = onFile.getMilitaryServiceEpisodes();

					for (Iterator i = episodes.iterator(); i.hasNext();) {
						MilitaryServiceEpisode obj = (MilitaryServiceEpisode) i
								.next();
						// Look for match
						if (this.getMatchRuleService().match(
								(MilitaryServiceEpisode) obj, incomingEpisode)) {
							mse = (MilitaryServiceEpisode) obj;
							break;
						}
					}
				} else {
					mse = onFile.getMilitaryServiceEpisodeByEntityKey(key);
				}
				if (mse == null) {
					mse = new MilitaryServiceEpisode();
					onFile.addMilitaryServiceEpisode(mse);
				}
				this.mergeMilitaryServiceEpisode(incomingEpisode, mse);
			}
		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge an MilitaryServiceEpisode", ex);
			throw new RuntimeException(
					"Failed to merge an MilitaryServiceEpisode", ex);
		}

		try {
			// Add/update ConflictExperiences
			Set conflictExps = incoming.getConflictExperiences();
			Set currentConflictExps = new HashSet(
					onFile.getConflictExperiences());
			Set mergedConflictExps = this.mergeSets(conflictExps,
					currentConflictExps);
			onFile.removeAllConflictExperiences();
			onFile.addAllConflictExperiences(mergedConflictExps);

			for (Iterator iter = currentConflictExps.iterator(); iter.hasNext();) {
				ConflictExperience currCE = (ConflictExperience) iter.next();
				boolean match = false;
				for (Iterator i = mergedConflictExps.iterator(); i.hasNext();) {
					ConflictExperience obj = (ConflictExperience) i.next();
					if (this.getMatchRuleService().match(
							(ConflictExperience) obj, currCE)) {
						match = true;
						break;
					}
				}
				if (!match) {
					onFile.addConflictExperience(currCE);
				}
			}
		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge a Combat Experience", ex);
			throw new RuntimeException("Failed to merge a Combat Experience",
					ex);
		}
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeMilitaryServiceNoDeletes(gov.va.med.esr.common.model.person.Person,
	 *      gov.va.med.esr.common.model.person.Person)
	 */
	public void mergeMilitaryServiceNoDeletes(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		if (incoming.getMilitaryService() != null) {
			MilitaryService militaryService = onFile.getMilitaryService();
			if (militaryService == null) {
				militaryService = new MilitaryService();
				onFile.setMilitaryService(militaryService);
			}
			this.getCopyService().copy(incoming.getMilitaryService(),
					militaryService);

			// Merge combat episodes
			// NO DELETES
			Set combatEpisodes = incoming.getMilitaryService()
					.getCombatEpisodes();
			Set currentCombatEpisodes = new HashSet(onFile.getMilitaryService()
					.getCombatEpisodes());
			Set mergedCombatEpisodes = this.mergeSets(combatEpisodes,
					currentCombatEpisodes);
			onFile.getMilitaryService().removeAllCombatEpisodes();
			// Put back updated or new episodes.
			onFile.getMilitaryService().addAllCombatEpisodes(
					mergedCombatEpisodes);
			// Put back any episodes that did not change.
			for (Iterator iter = currentCombatEpisodes.iterator(); iter
					.hasNext();) {
				CombatEpisode currCE = (CombatEpisode) iter.next();
				boolean match = false;
				for (Iterator i = mergedCombatEpisodes.iterator(); i.hasNext();) {
					CombatEpisode obj = (CombatEpisode) i.next();
					if (this.getMatchRuleService().match((CombatEpisode) obj,
							currCE)) {
						match = true;
						break;
					}
				}
				if (!match) {
					onFile.getMilitaryService().addCombatEpisode(currCE);
				}
			}

			// Merge combat services
			// NO DELETES
			Set combatServices = incoming.getMilitaryService()
					.getCombatServices();
			Set currentCombatServices = new HashSet(onFile.getMilitaryService()
					.getCombatServices());
			Set mergedCombatServices = this.mergeSets(combatServices,
					currentCombatServices);
			onFile.getMilitaryService().removeAllCombatServices();
			onFile.getMilitaryService().addAllCombatServices(
					mergedCombatServices);
			// Put back any objects that did not change
			for (Iterator iter = currentCombatServices.iterator(); iter
					.hasNext();) {
				CombatService currCS = (CombatService) iter.next();
				boolean match = false;
				for (Iterator i = mergedCombatServices.iterator(); i.hasNext();) {
					CombatService obj = (CombatService) i.next();
					if (this.getMatchRuleService().match((CombatService) obj,
							currCS)) {
						match = true;
						break;
					}
				}
				if (!match) {
					onFile.getMilitaryService().addCombatService(currCS);
				}
			}

			// Merge Activations
			// NO DELETES
			Set toAddActs = new HashSet();
			Set activations = incoming.getMilitaryService().getActivations();
			Set currentActivations = new HashSet(onFile.getMilitaryService()
					.getActivations());
			// This step does not merge Deployments.
			Set mergedActivations = this.mergeSets(activations,
					currentActivations);
			onFile.getMilitaryService().removeAllActivations();
			onFile.getMilitaryService().addAllActivations(mergedActivations);
			for (Iterator iter = currentActivations.iterator(); iter.hasNext();) {
				Activation currAct = (Activation) iter.next();
				Activation merge = null;
				for (Iterator i = mergedActivations.iterator(); i.hasNext();) {
					Activation obj = (Activation) i.next();
					if (this.getMatchRuleService().match((Activation) obj,
							currAct)) {
						merge = obj;
						break;
					}
				}
				if (merge == null) {
					// The Deployments are already there
					toAddActs.add(currAct);
				} else {
					// This will merge the Deployments
					mergeActivation(currAct, merge);
				}
			}
			if (!toAddActs.isEmpty()) {
				// Add the
				onFile.getMilitaryService().addAllActivations(toAddActs);
			}

			for (Iterator iter = mergedActivations.iterator(); iter.hasNext();) {
				Activation merge = (Activation) iter.next();
				Activation incomingAct = null;
				for (Iterator i = activations.iterator(); i.hasNext();) {
					incomingAct = (Activation) i.next();
					if (this.getMatchRuleService().match(incomingAct, merge)) {
						break;
					}
				}
				if (incomingAct != null) {
					this.mergeActivation(incomingAct, merge);
				}
			}

			// Merge site records
			Set siteRecords = incoming.getMilitaryService()
					.getMilitaryServiceSiteRecords();
			Set currentSiteRecords = new HashSet(onFile.getMilitaryService()
					.getMilitaryServiceSiteRecords());
			Set merged = this.mergeSets(siteRecords, currentSiteRecords);
			onFile.getMilitaryService().removeAllMilitaryServiceSiteRecords();

			// Add back unaffected records
			Set toAdd = new HashSet();
			for (Iterator iter = currentSiteRecords.iterator(); iter.hasNext();) {
				MilitaryServiceSiteRecord record = (MilitaryServiceSiteRecord) iter
						.next();
				MilitaryServiceSiteRecord merge = null;
				for (Iterator i = merged.iterator(); i.hasNext();) {
					MilitaryServiceSiteRecord obj = (MilitaryServiceSiteRecord) i
							.next();
					if (record.getSite().getCode()
							.equals(obj.getSite().getCode())) {
						merge = obj;
						break;
					}
				}
				if (merge == null) {
					toAdd.add(record);
				} else {
					// this step puts back anything that is missing from merged
					// object
					this.mergeMilitaryServiceSiteRecordNoDeletes(record, merge);
				}
			}

			if (!toAdd.isEmpty()) {
				onFile.getMilitaryService().addAllMilitaryServiceSiteRecords(
						toAdd);
			}

			for (Iterator iter = merged.iterator(); iter.hasNext();) {
				MilitaryServiceSiteRecord merge = (MilitaryServiceSiteRecord) iter
						.next();
				MilitaryServiceSiteRecord incomingSite = incoming
						.getMilitaryService()
						.getMilitaryServiceSiteRecordsBySite(merge.getSite());
				this.mergeMilitaryServiceSiteRecordNoDeletes(incomingSite,
						merge);
				onFile.getMilitaryService().addMilitaryServiceSiteRecord(merge);
			}

		}
	}

	/**
	 * Merges the MilitaryServiceEpisode objects.
	 *
	 * @param incoming
	 *            the incoming MilitaryServiceEpisode
	 * @param onFile
	 *            the MilitaryServiceEpisode on file
	 * @return MilitaryServiceEpisode on file after merging.
	 * @throws ServiceException
	 */
	public MilitaryServiceEpisode mergeMilitaryServiceEpisode(
			MilitaryServiceEpisode incoming, MilitaryServiceEpisode onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A MilitaryServiceEpisode must not be null");
		Validate.notNull(onFile,
				"A MilitaryServiceEpisode on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeCombatEpisode(gov.va.med.esr.common.model.ee.CombatEpisode,
	 *      gov.va.med.esr.common.model.ee.CombatEpisode)
	 */
	public CombatEpisode mergeCombatEpisode(CombatEpisode incoming,
			CombatEpisode onFile) {
		Validate.notNull(incoming, "A CombatEpisode must not be null");
		Validate.notNull(onFile, "A CombatEpisode on file must not be null");
		try {
			this.getCopyService().copy(incoming, onFile);
		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge an CombatEpisode", ex);
			throw new RuntimeException("Failed to merge an CombatEpisode", ex);
		}
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeCombatService(gov.va.med.esr.common.model.ee.CombatService,
	 *      gov.va.med.esr.common.model.ee.CombatService)
	 */
	public CombatService mergeCombatService(CombatService incoming,
			CombatService onFile) {
		Validate.notNull(incoming, "A CombatService must not be null");
		Validate.notNull(onFile, "A CombatService on file must not be null");
		try {
			this.getCopyService().copy(incoming, onFile);
		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge an CombatService", ex);
			throw new RuntimeException("Failed to merge an CombatService", ex);
		}
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeDeployment(gov.va.med.esr.common.model.ee.Deployment,
	 *      gov.va.med.esr.common.model.ee.Deployment)
	 */
	public Deployment mergeDeployment(Deployment incoming, Deployment onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A Deployment must not be null");
		Validate.notNull(onFile, "A Deployment on file must not be null");
		try {
			this.getCopyService().copy(incoming, onFile);
		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge an Deployment", ex);
			throw new RuntimeException("Failed to merge an Deployment", ex);
		}
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeActivation(gov.va.med.esr.common.model.ee.Activation,
	 *      gov.va.med.esr.common.model.ee.Activation)
	 */
	public Activation mergeActivation(Activation incoming, Activation onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An Activation must not be null");
		Validate.notNull(onFile, "An Activation on file must not be null");
		try {
			this.getCopyService().copy(incoming, onFile);
			/*
			 * Need to let rules figure which Deployment to add Set
			 * incomingDeployments = incoming.getDeployments(); Set
			 * currentDeployments = new HashSet(onFile.getDeployments()); Set
			 * mergedDeployments = mergeSets(incomingDeployments,
			 * currentDeployments); onFile.removeAllDeployments();
			 * onFile.addAllDeployments(mergedDeployments); for (Iterator iter =
			 * currentDeployments.iterator(); iter.hasNext();) { Deployment
			 * currDep = (Deployment)iter.next(); boolean match = false; for
			 * (Iterator i = mergedDeployments.iterator(); i.hasNext();) {
			 * Deployment obj = (Deployment) i.next(); if
			 * (this.getMatchRuleService().match((Deployment) obj, currDep)) {
			 * match = true; break; } } if (!match) {
			 * onFile.addDeployment(currDep); } }
			 */
		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge an Activation", ex);
			throw new RuntimeException("Failed to merge an Activation", ex);
		}
		return onFile;
	}

	/**
	 * Merges the ConflictExperience objects.
	 *
	 * @param incoming
	 *            the incoming ConflictExperience
	 * @param onFile
	 *            the ConflictExperience on file
	 * @return ConflictExperience on file after merging.
	 * @throws ServiceException
	 */
	public ConflictExperience mergeConflictExperience(
			ConflictExperience incoming, ConflictExperience onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A Combat Experience must not be null");
		Validate.notNull(onFile, "A Combat Experience on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * Merges the incoming purple heart to the result purple heart.
	 *
	 * @param incoming
	 *            the incoming purple heart
	 * @param result
	 *            the result purple heart
	 * @return result purple heart after merging
	 * @throws ServiceException
	 */
	public PurpleHeart mergePurpleHeart(PurpleHeart incoming, PurpleHeart result)
			throws ServiceException {
		Validate.notNull(incoming, "A incoming PurpleHeart must not be null");
		Validate.notNull(result, "A PurpleHeart on file must not be null");

		// Use copy to copy lookups and status data
		getCopyService().copy(incoming, result);

		// Merge documents
		Set mergedDocuments = mergeSets(incoming.getDocuments(),
				result.getDocuments());

		// Remove all the documents and add merged set
		result.removeAllDocuments();

		for (Iterator iter = mergedDocuments.iterator(); iter.hasNext();) {
			result.addDocument((PurpleHeartDocument) iter.next());
		}

		return result;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeApplication(gov.va.med.esr.common.model.ee.Application,
	 *      gov.va.med.esr.common.model.ee.Application)
	 */
	public Application mergeApplication(Application incoming, Application onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An application must not be null");
		Validate.notNull(onFile, "An application on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeCancelDecline(gov.va.med.esr.common.model.ee.CancelDecline,
	 *      gov.va.med.esr.common.model.ee.CancelDecline)
	 */
	public CancelDecline mergeCancelDecline(CancelDecline incoming,
			CancelDecline onFile) throws ServiceException {
		Validate.notNull(incoming, "A CancelDecline must not be null");
		Validate.notNull(onFile, "A CancelDecline on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeCancelDecline(gov.va.med.esr.common.model.ee.CancelDecline,
	 *      gov.va.med.esr.common.model.person.Person)
	 */
	public CancelDecline mergeCancelDecline(CancelDecline incoming,
			Person onFile) throws ServiceException {
		Validate.notNull(onFile, "An on file person must not be null");

		CancelDecline cancelDecline = null;
		if (incoming != null) {
			cancelDecline = onFile.getCancelDecline();
			if (cancelDecline == null) {
				cancelDecline = new CancelDecline();
				onFile.setCancelDecline(cancelDecline);
			}
			mergeCancelDecline(incoming, cancelDecline);
		} else {
			onFile.setCancelDecline(null);
		}
		return cancelDecline;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeEligibilityVerification(gov.va.med.esr.common.model.ee.EligibilityVerification,
	 *      gov.va.med.esr.common.model.ee.EligibilityVerification)
	 */
	public EligibilityVerification mergeEligibilityVerification(
			EligibilityVerification incoming, EligibilityVerification onFile)
			throws ServiceException {
		Validate.notNull(incoming,
				"An EligibilityVerification must not be null");
		Validate.notNull(onFile,
				"An EligibilityVerification on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeEligibilityVerification(gov.va.med.esr.common.model.ee.EligibilityVerification,
	 *      gov.va.med.esr.common.model.ee.EligibilityVerification)
	 */
	public EligibilityVerification mergeEligibilityVerification(
			EligibilityVerification incoming, Person onFile)
			throws ServiceException {

		Validate.notNull(onFile, "An onFile person must not be null ");

		EligibilityVerification updated = onFile.getEligibilityVerification();
		if (incoming != null) {
			if (updated == null) {
				updated = new EligibilityVerification();
				onFile.setEligibilityVerification(updated);
			}
			this.mergeEligibilityVerification(incoming, updated);
		} else {
			updated = null;
			onFile.setEligibilityVerification(null);
		}
		return updated;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeIncompetenceRuling(gov.va.med.esr.common.model.ee.IncompetenceRuling,
	 *      gov.va.med.esr.common.model.ee.IncompetenceRuling)
	 */
	public IncompetenceRuling mergeIncompetenceRuling(
			IncompetenceRuling incoming, IncompetenceRuling onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An IncompetenceRuling must not be null");
		Validate.notNull(onFile,
				"An IncompetenceRuling on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeIncompetenceRuling(gov.va.med.esr.common.model.ee.IncompetenceRuling,
	 *      gov.va.med.esr.common.model.ee.IncompetenceRuling)
	 */
	public IncompetenceRuling mergeIncompetenceRuling(
			IncompetenceRuling incoming, Person onFile) throws ServiceException {

		Validate.notNull(onFile, "An onFile person must not be null ");

		IncompetenceRuling updated = null;
		if (incoming != null) {
			updated = onFile.getIncompetenceRuling();
			if (updated == null) {
				updated = new IncompetenceRuling();
				onFile.setIncompetenceRuling(updated);
			}
			this.mergeIncompetenceRuling(incoming, updated);
		} else {
			onFile.setIncompetenceRuling(null);
		}
		return updated;
	}

	/**
	 * (non-Javadoc)
	 *
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeMedicaidFactor(gov.va.med.esr.common.model.ee.MedicaidFactor,
	 *      gov.va.med.esr.common.model.ee.MedicaidFactor)
	 */
	public MedicaidFactor mergeMedicaidFactor(MedicaidFactor incoming,
			MedicaidFactor onFile) throws ServiceException {
		Validate.notNull(incoming, "A MedicaidFactor must not be null");
		Validate.notNull(onFile, "A MedicaidFactor on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeMonetaryBenefit(gov.va.med.esr.common.model.ee.MonetaryBenefit,
	 *      gov.va.med.esr.common.model.ee.MonetaryBenefit)
	 */
	public MonetaryBenefit mergeMonetaryBenefit(MonetaryBenefit incoming,
			MonetaryBenefit onFile) throws ServiceException {
		Validate.notNull(incoming, "A MonetaryBenefit must not be null");
		Validate.notNull(onFile, "A MonetaryBenefit on file must not be null");

		this.getCopyService().copy(incoming, onFile);

		processPensionData(incoming, onFile);

		return onFile;
	}

	// CCR 10352:
	// SUC1869.28 – If the actor successfully changes the Pension Award
	// Effective Date
	// then the system deletes the Pension Award Termination Date and the
	// Pension Award Termination Codes 1 – 4

	// BRn19.1.2.1.1 Pension Award Termination Code Constraints
	// The Pension Award Termination Codes are required in sequence from 1 - 4.
	// If Pension Award Termination Code 1 is deleted then Pension Award
	// Termination Code 2 is moved to Pension Award Termination Code 1
	// and the remaining codes are moved up to fill any vacated codes in the
	// sequence.

	private void processPensionData(MonetaryBenefit incoming,
			MonetaryBenefit onFile) throws CopyServiceException,
			UnknownLookupTypeException, UnknownLookupCodeException {

		Pension in = null;

		if (incoming != null && incoming.getPension() != null) {
			// incoming has pension data
			in = incoming.getPension();

			// SUC1869.28 – If the actor successfully changes the Pension Award
			// Effective Date
			// then the system deletes the Pension Award Termination Date and
			// the Pension Award Termination Codes 1 – 4
			if (incoming.getMonetaryBenefitIndicator() != null
					&& Boolean.TRUE.equals(incoming
							.getMonetaryBenefitIndicator().toBoolean())
					&& in.getEffectiveDate() != null) {
				in.setTerminationDate(null);
				in.setTerminationReasonCode1(null);
				in.setTerminationReasonCode2(null);
				in.setTerminationReasonCode3(null);
				in.setTerminationReasonCode4(null);

			} else {
				if (incoming.getMonetaryBenefitIndicator() != null
						&& Boolean.FALSE.equals(incoming
								.getMonetaryBenefitIndicator().toBoolean())
						&& in.getTerminationDate() != null) {
					in.setEffectiveDate(null);
					in.setAwardReasonCode(null);

					compactTerminationReasons(in);
				}
			}

			/*
			 * if (onFile.getPension() != null &&
			 * this.getMatchRuleService().match(in, onFile.getPension())) {
			 * //same pension, no need to process return; }
			 */

			//CCR 11262
			Pension p = onFile.getPension();
			if (p == null)
			{
				p = new Pension();
				p.setMonetaryBenefit(onFile);
				onFile.setPension(p);
			}


			// this.getCopyService().copy(p, onFile.getPension());
			this.copyPensionData(in, onFile.getPension());
		} else {
			// no incoming Pension, set onFile null
			if (onFile.getPension() != null) {
				onFile.setPension(null);
			}
		}

	}

	private void compactTerminationReasons(Pension p) {
		if (p != null) {
			PensionReasonCode[] codes = new PensionReasonCode[4];
			codes[0] = p.getTerminationReasonCode1();
			codes[1] = p.getTerminationReasonCode2();
			codes[2] = p.getTerminationReasonCode3();
			codes[3] = p.getTerminationReasonCode4();

			// let the non-null value bubble up
			for (int j = 0; j < 3; j++) {
				for (int i = 0; i < 3; i++) {
					if (codes[i] == null) {
						codes[i] = codes[i + 1];
						codes[i + 1] = null;
					}
				}
			}

			p.setTerminationReasonCode1(codes[0]);
			p.setTerminationReasonCode2(codes[1]);
			p.setTerminationReasonCode3(codes[2]);
			p.setTerminationReasonCode4(codes[3]);
		}
	}

	private void copyPensionData(Pension incoming, Pension onFile) {
		// transfer pension data
		onFile.setAwardReasonCode(incoming.getAwardReasonCode());
		onFile.setEffectiveDate(incoming.getEffectiveDate());
		onFile.setTerminationDate(incoming.getTerminationDate());
		onFile.setTerminationReasonCode1(incoming.getTerminationReasonCode1());
		onFile.setTerminationReasonCode2(incoming.getTerminationReasonCode2());
		onFile.setTerminationReasonCode3(incoming.getTerminationReasonCode3());
		onFile.setTerminationReasonCode4(incoming.getTerminationReasonCode4());

		//CCR 11262 commented out below
		//onFile.setMonetaryBenefit(incoming.getMonetaryBenefit());

	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeMonetaryBenefitAward(gov.va.med.esr.common.model.ee.MonetaryBenefitAward,
	 *      gov.va.med.esr.common.model.ee.MonetaryBenefitAward)
	 */
	public MonetaryBenefitAward mergeMonetaryBenefitAward(
			MonetaryBenefitAward incoming, Person onFile)
			throws ServiceException {

		Validate.notNull(onFile, "An onFile person must not be null ");

		MonetaryBenefitAward updated = onFile.getMonetaryBenefitAward();
		if (incoming != null) {
			if (updated == null) {
				updated = new MonetaryBenefitAward();
				onFile.setMonetaryBenefitAward(updated);
			}
			this.mergeMonetaryBenefitAward(incoming, updated);
		} else {
			updated = null;
			onFile.setMonetaryBenefitAward(null);
		}
		return updated;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeMonetaryBenefitAward(gov.va.med.esr.common.model.ee.MonetaryBenefitAward,
	 *      gov.va.med.esr.common.model.ee.MonetaryBenefitAward)
	 */
	public MonetaryBenefitAward mergeMonetaryBenefitAward(
			MonetaryBenefitAward incoming, MonetaryBenefitAward onFile)
			throws ServiceException {

		Validate.notNull(incoming, "A MonetaryBenefitAward must not be null");
		Validate.notNull(onFile,
				"A MonetaryBenefitAward on file must not be null");

		try {
			// Must do unconditional copy
			this.getCopyService().copy(incoming, onFile);

			Set mbsToRemove = new HashSet();
			// Identify Monetary Benefits to delete
			Set onFileMBAs = new HashSet(onFile.getMonetaryBenefits());
			for (Iterator iter = onFileMBAs.iterator(); iter.hasNext();) {
				MonetaryBenefit mb = (MonetaryBenefit) iter.next();
				boolean found = false;
				for (Iterator iterMBs = incoming.getMonetaryBenefits()
						.iterator(); iterMBs.hasNext();) {
					MonetaryBenefit incomingMB = (MonetaryBenefit) iterMBs
							.next();
					if (this.getMatchRuleService().match(mb, incomingMB)) {
						// must copy since a match does not mean attributes the
						// same
						this.mergeMonetaryBenefit(incomingMB, mb);
						found = true;
						break;
					}

				}
				if (!found) {
					mbsToRemove.add(mb);
				}
			}

			// Delete the monetary benefits
			for (Iterator iter = mbsToRemove.iterator(); iter.hasNext();) {
				MonetaryBenefit remove = (MonetaryBenefit) iter.next();
				onFile.removeMonetaryBenefit(remove);
			}

			// Add/update Monetary Benefits
			for (Iterator iter = incoming.getMonetaryBenefits().iterator(); iter
					.hasNext();) {
				MonetaryBenefit incomingMB = (MonetaryBenefit) iter.next();
				boolean found = false;
				for (Iterator iterOnFiles = onFile.getMonetaryBenefits()
						.iterator(); iterOnFiles.hasNext();) {
					MonetaryBenefit onFileMB = (MonetaryBenefit) iterOnFiles
							.next();
					if (this.getMatchRuleService().match(onFileMB, incomingMB)) {
						// Found an object with same values
						found = true;
						break;
					}
				}
				// Add if new
				if (!found) {
					MonetaryBenefit mb = this.mergeMonetaryBenefit(incomingMB,
							new MonetaryBenefit());
					onFile.addMonetaryBenefit(mb);
				}
			}
		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge a MonetaryBenefitAward", ex);
			throw new RuntimeException(
					"Failed to merge a MonetaryBenefitAward", ex);
		}

		return onFile;
	}

	/**
	 *
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeRatedDisability(gov.va.med.esr.common.model.ee.RatedDisability,
	 *      gov.va.med.esr.common.model.ee.RatedDisability)
	 */
	public RatedDisability mergeRatedDisability(RatedDisability incoming,
			RatedDisability onFile) throws ServiceException {
		Validate.notNull(incoming, "A RatedDisability must not be null");
		Validate.notNull(onFile, "A RatedDisability on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeServiceConnectionAward(gov.va.med.esr.common.model.ee.ServiceConnectionAward,
	 *      gov.va.med.esr.common.model.ee.ServiceConnectionAward)
	 */
	public ServiceConnectionAward mergeServiceConnectionAward(
			ServiceConnectionAward incoming, Person onFile)
			throws ServiceException {

		Validate.notNull(onFile, "An onFile person must not be null ");

		ServiceConnectionAward updated = onFile.getServiceConnectionAward();
		if (incoming != null) {
			if (updated == null) {
				updated = new ServiceConnectionAward();
				onFile.setServiceConnectionAward(updated);
			}
			this.mergeServiceConnectionAward(incoming, updated);
		} else {
			updated = null;
			onFile.setServiceConnectionAward(null);
		}
		return updated;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeServiceConnectionAward(gov.va.med.esr.common.model.ee.ServiceConnectionAward,
	 *      gov.va.med.esr.common.model.ee.ServiceConnectionAward)
	 */
	public ServiceConnectionAward mergeServiceConnectionAward(
			ServiceConnectionAward incoming, ServiceConnectionAward onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A ServiceConnectionAward must not be null");
		Validate.notNull(onFile,
				"A ServiceConnectionAward on file must not be null");
		try {
			// Update unconditionally since match service will return match if
			// both have keys. Note that this copy will NOT copy child objects.
			this.getCopyService().copy(incoming, onFile);

			Set onFileRDs = new HashSet(onFile.getRatedDisabilities());

			onFile.removeAllRatedDisability();
			Set addRds = new HashSet();

			for (Iterator iterRDs = incoming.getRatedDisabilities().iterator(); iterRDs
					.hasNext();) {
				RatedDisability incomingRD = (RatedDisability) iterRDs.next();
				RatedDisability found = null;

				for (Iterator iter = onFileRDs.iterator(); iter.hasNext();) {
					RatedDisability rd = (RatedDisability) iter.next();

					if (this.getMatchRuleService().match(rd, incomingRD)) {
						// Must copy data since match does not mean all
						// attributes are same.
						this.mergeRatedDisability(incomingRD, rd);
						found = rd;
						break;
					}
				}
				if (found != null) {
					onFileRDs.remove(found);
					addRds.add(found);
				} else {
					RatedDisability newRd = new RatedDisability();
					this.mergeRatedDisability(incomingRD, newRd);
					addRds.add(newRd);
				}
			}

			for (Iterator iter = addRds.iterator(); iter.hasNext();) {
				RatedDisability rd = (RatedDisability) iter.next();
				onFile.addRatedDisability(rd);
			}
		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge a ServiceConnectionAward", ex);
			throw new RuntimeException(
					"Failed to merge a ServiceConnectionAward", ex);
		}
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeSpecialFactor(gov.va.med.esr.common.model.ee.SpecialFactor,
	 *      gov.va.med.esr.common.model.ee.SpecialFactor)
	 */
	public SpecialFactor mergeSpecialFactor(SpecialFactor incoming,
			SpecialFactor onFile) throws ServiceException {
		Validate.notNull(incoming, "A SpecialFactor must not be null");
		Validate.notNull(onFile, "A SpecialFactor must not be null");
		if(incoming.getClass().getName().contains("Camp"))
		{
			mergeCampLejeuneVerification((CampLejeuneVerification)incoming,(CampLejeuneVerification)onFile);
		}
		else
		 this.getCopyService().copy(incoming, onFile);
		return onFile;
	}


	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeSpecialFactor(gov.va.med.esr.common.model.ee.SpecialFactor,
	 *      gov.va.med.esr.common.model.person.Person)
	 */
	public SpecialFactor mergeSpecialFactor(SpecialFactor incoming,
			Person onFile) throws ServiceException {
		Validate.notNull(incoming, "A SpecialFactor must not be null");
		Validate.notNull(onFile, "An on file person must not be null");

		SpecialFactor found = onFile
				.getSpecialFactorByType(incoming.getClass());
		if (found != null) {
			mergeSpecialFactor(incoming, found);
		} else {
			try {
				SpecialFactor newSf = (SpecialFactor) incoming.getClass()
						.newInstance();
				mergeSpecialFactor(incoming, newSf);
				onFile.addSpecialFactor(newSf);
				found = newSf;
			} catch (Exception e) {
				this.logger.debug("Failed to merge a special factor", e);
				throw new RuntimeException("Failed to merge a special factor",
						e);
			}
		}
		return found;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeSpecialFactor(gov.va.med.esr.common.model.person.Person,
	 *      gov.va.med.esr.common.model.person.Person)
	 */
	public void mergeSpecialFactor(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An incoming person must not be null");
		Validate.notNull(onFile, "An on file person must not be null");

		Set factors = incoming.getSpecialFactors();
		if (factors != null && factors.size() > 0) {
			Set merged = this.mergeSets(factors, onFile.getSpecialFactors());
			onFile.removeAllSpecialFactors();
			for (Iterator iter = merged.iterator(); iter.hasNext();) {
				SpecialFactor factor = (SpecialFactor) iter.next();
				onFile.addSpecialFactor(factor);
			}
		} else {
			onFile.removeAllSpecialFactors();
		}
	}

	public CatastrophicDisability mergeCatastrophicDisability(CatastrophicDisability incoming,
			CatastrophicDisability onFile) throws ServiceException {
		// Allow existing callers to work correctly
		return mergeCatastrophicDisability(incoming, onFile, true);
	}

	/**
	 * Merges the CatastrophicDisability objects.
	 *
	 * @param incoming
	 *            the incoming CatastrophicDisability
	 * @param onFile
	 *            the CatastrophicDisability on file
	 * @return CatastrophicDisability on file after merging.
	 * @throws ServiceException
	 */
		public CatastrophicDisability mergeCatastrophicDisability(
				CatastrophicDisability incoming, CatastrophicDisability onFile,
				boolean isFromUI) throws ServiceException {
		Validate.notNull(incoming, "A CatastrophicDisability must not be null");
		Validate.notNull(onFile, "A CatastrophicDisability must not be null");
		getCopyService().copy(incoming, onFile);

		// Don't lose the existing determining facility.
		if (incoming.getDeterminationFacility() != null) {
			onFile.setDeterminationFacility(incoming.getDeterminationFacility());
		}

		// Update the CD Descriptors
		mergeCatastrophicDisabilityCDDescriptorsOnly(incoming, onFile, false);

		// PROBLEM: Some sites may not switch to CDDescriptors right away.
		// ASSUMPTION: Vista will NOT send cat disabled=Yes without c/p/d data OR descriptors.
		// We must preserve c/p/d data once system switches to descriptors.
		// Skipping the following code block leaves CPD data intact.
		boolean descriptorExists = onFile.getCDDescriptors().size() > 0;
		boolean cpdDataWasProvided = (incoming.getConditions().size() > 0) || (incoming.getProcedures().size() > 0) ||
			(incoming.getDiagnoses().size() > 0);

		/**
		 * Three conditions supported to enter following code block:
		 *
		 * 1. Must allow person merge to operate on CPD data (add/delete) thus the isFromUI flag.
		 *
		 * 2. The cat disabled could be Yes or No. If CDDescriptors don't exist on person,
		 * we must assume that message contained old structures so we handle the
		 * old way. BUT it is possible that cat disabled is No and descriptors were removed.
		 * In that case all the CPD data will be removed since incoming will not contain CPD
		 * data.
		 *
		 * 3. Must handle the case where ES receives descriptors for person with no
		 * prior CD data. This happens because we do on-the-fly conversion. If the incoming CPD data gets
		 * converted, the CPD won't be saved unless we make an exception. We can recognize
		 * a person that was converted on the fly since they will have both DES and CPD data.
		 * Normally the incoming person from non-UI will have one or the other.
		 */
		if (isFromUI || !descriptorExists || (descriptorExists && cpdDataWasProvided)) {
			// Update the CD Conditions
			Set conditions = mergeSets(incoming.getConditions(),
					onFile.getConditions());
			onFile.removeAllConditions();
			for (Iterator iter = conditions.iterator(); iter.hasNext();) {
				onFile.addCondition((CDCondition) iter.next());
			}

			// Update the CD Procedures
			Set procedures = mergeSets(incoming.getProcedures(),
					onFile.getProcedures());
			onFile.removeAllProcedures();
			for (Iterator iter = procedures.iterator(); iter.hasNext();) {
				onFile.addProcedure((CDProcedure) iter.next());
			}

			// Update the CD Diagnosis
			Set diagnoses = mergeSets(incoming.getDiagnoses(),
					onFile.getDiagnoses());
			onFile.removeAllDiagnoses();
			for (Iterator iter = diagnoses.iterator(); iter.hasNext();) {
				onFile.addDiagnosis((CDDiagnosis) iter.next());
			}
		}
		return onFile;
	}

	/**
	 * This method has very specific use for scenario where only want to to accept
	 * CDDescriptors. That usage is specific to scenario where sending site is not the
	 * Facility Making Determination. This method can be used as part of CD merge where that
	 * merge handles the actual CD merge, etc.
	 *
	 * Merges the CatastrophicDisability's CDDescriptor objects ONLY. Leaves intact
	 * existing conditions/procedures/diagnoses. Does not update CD attributes.
	 *
	 * @param source
	 *            the incoming CatastrophicDisability
	 * @param target
	 *            the CatastrophicDisability on file
	 * @param addOnly
	 * 			  only adding only of CDDescriptors
	 * @return CatastrophicDisability on file after merging.
	 * @throws ServiceException
	 */
	public CatastrophicDisability mergeCatastrophicDisabilityCDDescriptorsOnly(
			CatastrophicDisability incoming, CatastrophicDisability onFile, boolean addOnly)
			throws ServiceException {
		Validate.notNull(incoming, "A CatastrophicDisability must not be null");
		Validate.notNull(onFile, "A CatastrophicDisability must not be null");

		// Don't copy anything from incoming to the onFile except for the CDDescriptors.
		// The intent is to only update the onFile with CDDescriptors.
		if (ObjectUtils.equals(incoming.getCDDescriptors().size(), 0)) {
			onFile.removeAllCDDescriptors();
			return onFile;
		}
		boolean skipMergeSets = true;

		if (ObjectUtils.equals(incoming.getCDDescriptors().size(), onFile.getCDDescriptors().size())) {
			for (CDDescriptor descriptor : incoming.getCDDescriptors()) {
				CDDescriptor matched = (CDDescriptor)getMatchRuleService().findMatchingElement(descriptor, onFile.getCDDescriptors());
				if (matched == null) {
					skipMergeSets = false;
					break;
				}
			}
		} else {
			skipMergeSets = false;
		}

		// Update the CD Descriptors. Avoid creating new objects by NOT calling mergeSets.
		// This minimizes number of history records as Hibernate will not see changes.
		if (!skipMergeSets) {
			if (!addOnly) {
				// ALLOW ADD and DELETES
				Set<CDDescriptor> descriptorsHold = new HashSet<CDDescriptor>(onFile.getCDDescriptors());
				Set<CDDescriptor> toAdd = new HashSet<CDDescriptor>();
				onFile.removeAllCDDescriptors();
				for (CDDescriptor descriptor : incoming.getCDDescriptors()) {
					CDDescriptor matched = (CDDescriptor)getMatchRuleService().findMatchingElement(descriptor,
							descriptorsHold);
					if (matched != null) {
						toAdd.add(matched);
					}
					else {
						CDDescriptor newCDD = new CDDescriptor();
						newCDD.setDescriptorType(descriptor.getDescriptorType());
						toAdd.add(newCDD);
					}
				}

				for (CDDescriptor descriptor : toAdd) {
					onFile.addCDDescriptor(descriptor);
				}
			} else {
				// ADD ONLY
				Set<CDDescriptor> toAdd = new HashSet<CDDescriptor>();
				for (CDDescriptor descriptor : incoming.getCDDescriptors()) {
					CDDescriptor matched = (CDDescriptor)getMatchRuleService().findMatchingElement(descriptor,
							onFile.getCDDescriptors());
					if (matched == null) {
						CDDescriptor newCDD = new CDDescriptor();
						newCDD.setDescriptorType(descriptor.getDescriptorType());
						toAdd.add(newCDD);
					}
				}
				for (CDDescriptor descriptor : toAdd) {
					onFile.addCDDescriptor(descriptor);
				}
			}
		}

		return onFile;
	}

	/**
	 * Merges the MilitarySexualTrauma objects.
	 *
	 * @param incoming
	 *            the incoming MilitarySexualTrauma
	 * @param onFile
	 *            the MilitarySexualTrauma on file
	 * @return MilitarySexualTrauma on file after merging.
	 * @throws ServiceException
	 */
	public MilitarySexualTrauma mergeMilitarySexualTrauma(
			MilitarySexualTrauma incoming, MilitarySexualTrauma onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A MilitarySexualTrauma must not be null");
		Validate.notNull(onFile, "A MilitarySexualTrauma must not be null");
		getCopyService().copy(incoming, onFile);
		// Donot loose the existing determining facility.
		if (incoming.getDeterminationFacility() != null) {
			onFile.setDeterminationFacility(incoming.getDeterminationFacility());
		}
		return onFile;
	}

	/**
	 * Merges the NoseThroatRadium objects.
	 *
	 * @param incoming
	 *            the incoming NoseThroatRadium
	 * @param onFile
	 *            the NoseThroatRadium on file
	 * @return NoseThroatRadium on file after merging.
	 * @throws ServiceException
	 */
	public NoseThroatRadium mergeNoseThroatRadium(NoseThroatRadium incoming,
			NoseThroatRadium onFile) throws ServiceException {
		Validate.notNull(incoming, "A NoseThroatRadium must not be null");
		Validate.notNull(onFile, "A NoseThroatRadium must not be null");
		getCopyService().copy(incoming, onFile);
		// Donot loose the existing determining facility.
		if (incoming.getDeterminationFacility() != null) {
			onFile.setDeterminationFacility(incoming.getDeterminationFacility());
		}
		return onFile;
	}

	/**
	 * Merges the FinancialStatement Objects
	 *
	 * @param incoming
	 * @param onFile
	 * @return FinancialStatement
	 * @throws ServiceException
	 */
	public FinancialStatement mergeFinancialStatement(
			FinancialStatement incoming, FinancialStatement onFile)
			throws ServiceException {
		return this.mergeFinancialStatement(incoming, onFile, false);
	}

	public FinancialStatement mergeFinancialStatement(
			FinancialStatement incoming, FinancialStatement onFile,
			boolean isMergeForZ07) throws ServiceException {
		Validate.notNull(incoming, "A FinancialStatement must not be null");
		Validate.notNull(onFile, "A FinancialStatement must not be null");

		// Copy the specified fields
		getCopyService().copy(incoming, onFile);

		// Merge Financial Information Data
		mergeFinancialInformation(incoming, onFile);

		// Match and Merge Spouse financials
		removeDeletedSpouseFinancials(incoming, onFile);
		Set incomingSpouseFinancials = incoming.getSpouseFinancials();
		Set onFileSpouseFinancials = onFile.getSpouseFinancials();

		for (Iterator i = incomingSpouseFinancials.iterator(); i.hasNext();) {
			SpouseFinancials sourceFinancials = (SpouseFinancials) i.next();

			// Find the matching one from onFile
			SpouseFinancials targetFinancials = (SpouseFinancials) matchRuleService
					.findMatchingElement(sourceFinancials,
							onFileSpouseFinancials);
			if (targetFinancials == null) {
				targetFinancials = new SpouseFinancials();
				mergeSpouseFinancials(sourceFinancials, targetFinancials,
						isMergeForZ07);
				onFile.addSpouseFinancials(targetFinancials);
			} else {
				mergeSpouseFinancials(sourceFinancials, targetFinancials,
						isMergeForZ07);
			}
		}
		// Match and Merge dependent financials
		removeDeletedDependentFinancials(incoming, onFile);
		Set onFileDependentFinancials = onFile.getDependentFinancials();

		Set incomingDependentFinancials = incoming.getDependentFinancials();
		for (Iterator i = incomingDependentFinancials.iterator(); i.hasNext();) {
			DependentFinancials sourceFinancials = (DependentFinancials) i
					.next();
			// Find the matching one from onFile
			DependentFinancials targetFinancials = (DependentFinancials) matchRuleService
					.findMatchingElement(sourceFinancials,
							onFileDependentFinancials);
			if (targetFinancials == null) {
				targetFinancials = new DependentFinancials();
				mergeDependentFinancials(sourceFinancials, targetFinancials,
						isMergeForZ07);
				onFile.addDependentFinancials(targetFinancials);
			} else {
				mergeDependentFinancials(sourceFinancials, targetFinancials,
						isMergeForZ07);
			}
		}
		return onFile;
	}

	/**
	 * remove deleted spouse financials from financial statement
	 *
	 * @param incoming
	 * @param onFile
	 */
	private void removeDeletedSpouseFinancials(FinancialStatement incoming,
			FinancialStatement onFile) {
		Set incomingSpouseFinancials = incoming.getSpouseFinancials();
		Set onFileSpouseFinancials = onFile.getSpouseFinancials();
		Set spousesToBeDeleted = new HashSet();

		// Remove all the deleted spouse financials
		for (Iterator i = onFileSpouseFinancials.iterator(); i.hasNext();) {
			SpouseFinancials spouseFinancials = (SpouseFinancials) i.next();
			if (matchRuleService.findMatchingElement(spouseFinancials,
					incomingSpouseFinancials) == null) {
				// No match found remove
				spousesToBeDeleted.add(spouseFinancials);
				if (logger.isDebugEnabled()) {
					logger.debug(" Removing Spouse Finacials with id "
							+ spouseFinancials.getEntityKey()
									.getKeyValueAsString());
				}
			}
		}
		if (spousesToBeDeleted.size() > 0) {
			onFile.removeSpouseFinancials(spousesToBeDeleted);
		}
	}

	/**
	 * remove deleted dependent financials from financial statement
	 *
	 * @param incoming
	 * @param onFile
	 */
	private void removeDeletedDependentFinancials(FinancialStatement incoming,
			FinancialStatement onFile) {
		Set incomingDependentFinancials = incoming.getDependentFinancials();
		Set onFileDependentFinancials = onFile.getDependentFinancials();
		Set dependentsToBeDeleted = new HashSet();

		// Remove all the deleted dependent financials
		for (Iterator i = onFileDependentFinancials.iterator(); i.hasNext();) {
			DependentFinancials dependentFinancials = (DependentFinancials) i
					.next();
			if (matchRuleService.findMatchingElement(dependentFinancials,
					incomingDependentFinancials) == null) {
				// No match found remove
				dependentsToBeDeleted.add(dependentFinancials);
				if (logger.isDebugEnabled()) {
					logger.debug(" Removing Dependent Finacials with id "
							+ dependentFinancials.getEntityKey()
									.getKeyValueAsString());
				}
			}
		}
		if (dependentsToBeDeleted.size() > 0) {
			onFile.removeDependentFinancials(dependentsToBeDeleted);
		}
	}

	/**
	 * Copy financialInformation (which is common to Veteran/Dependent/Spouse)
	 *
	 * @param incoming
	 * @param onFile
	 * @return
	 * @throws ServiceException
	 */
	public FinancialInformation mergeFinancialInformation(
			FinancialInformation incoming, FinancialInformation onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A FinancialStatement must not be null");
		Validate.notNull(onFile, "A FinancialStatement must not be null");

		// copy financial information
		// Assets
		Map assets = mergeMaps(incoming.getAssets(), onFile.getAssets());
		for (Iterator i = assets.keySet().iterator(); i.hasNext();) {
			AssetType assetType = (AssetType) i.next();
			onFile.setAsset(assetType, (Asset) assets.get(assetType));
		}

		// Income
		Map incomes = mergeMaps(incoming.getIncome(), onFile.getIncome());
		for (Iterator i = incomes.keySet().iterator(); i.hasNext();) {
			IncomeType incomeType = (IncomeType) i.next();
			onFile.setIncome(incomeType, (Income) incomes.get(incomeType));
		}

		// Expenditure
		Map expenses = mergeMaps(incoming.getExpenses(), onFile.getExpenses());
		for (Iterator i = expenses.keySet().iterator(); i.hasNext();) {
			ExpenseType expenseType = (ExpenseType) i.next();
			onFile.setExpense(expenseType, (Expense) expenses.get(expenseType));
		}

		mergeDebt(incoming, onFile);
		return onFile;
	}

	/**
	 * @param incoming
	 * @param onFile
	 */
	private void mergeDebt(FinancialInformation incoming,
			FinancialInformation onFile) {
		// Debt
		Debt incomingDebt = incoming.getDebt();
		Debt onFileDebt = onFile.getDebt();
		if (incomingDebt == null && onFileDebt == null) {
			// nothing to do here.
			return;
		} else if (incomingDebt == null && onFileDebt != null) {
			onFile.setDebt(null);
		} else if (incomingDebt != null && onFileDebt == null) {
			onFile.setDebt(new Debt(incomingDebt.getAmount()));
		} else if (incomingDebt != null && onFileDebt != null) {
			onFileDebt.setAmount(incomingDebt.getAmount());
		}
	}

	/**
	 * Merge the Map objects from source to destination
	 */
	private Map mergeMaps(Map incoming, Map onFile) throws CopyServiceException {
		Map mergedMap = new HashMap();
		Validate.notNull(incoming, "A incoming map must not be null");
		Validate.notNull(onFile, "A onFile map must not be null");
		Set deletedKeys = new HashSet(onFile.keySet());
		deletedKeys.removeAll(incoming.keySet());
		Set newKeys = new HashSet(incoming.keySet());
		newKeys.removeAll(onFile.keySet());
		Set updatedKeys = new HashSet(incoming.keySet());
		updatedKeys.removeAll(newKeys);
		// Set null for deleted items
		for (Iterator i = deletedKeys.iterator(); i.hasNext();) {
			Object key = i.next();
			mergedMap.put(key, null);
		}
		// Create a new instance
		for (Iterator i = newKeys.iterator(); i.hasNext();) {
			Object key = i.next();
			try {
				Object newInstance = incoming.get(key).getClass().newInstance();
				getCopyService().copy((AbstractEntity) incoming.get(key),
						(AbstractEntity) newInstance);
				mergedMap.put(key, newInstance);
			} catch (Exception e) {
				throw new CopyServiceException(
						"Unable to create new Instance for "
								+ incoming.get(key).getClass().getName());
			}
		}
		// Use the copy services for modified ones
		for (Iterator i = updatedKeys.iterator(); i.hasNext();) {
			Object key = i.next();
			getCopyService().copy((AbstractEntity) incoming.get(key),
					(AbstractEntity) onFile.get(key));
			mergedMap.put(key, onFile.get(key));
		}
		return mergedMap;
	}

	/**
	 * Merge the full income test. This includes all the properties that are
	 * excluded in the commons.xml excluding person.
	 *
	 * @param incomingTest
	 * @param resultTest
	 * @throws ServiceException
	 */
	public void mergeFullIncomeTest(IncomeTest incomingTest,
			IncomeTest resultTest) throws ServiceException {
		this.mergeIncomeTest(incomingTest, resultTest);
		// copy all the attributes that are excluded in the commons.xml
		resultTest.setGmtThresholdAmount(incomingTest.getGmtThresholdAmount());
		Hardship incomingHardship = incomingTest.getHardship();
		Hardship onFileHardship = resultTest.getHardship();
		if (incomingHardship == null) {
			resultTest.setHardship(null);
		} else {
			if (onFileHardship == null) {
				onFileHardship = new Hardship();
				resultTest.setHardship(onFileHardship);
			}
			this.mergeHardship(incomingHardship, onFileHardship);
		}
		resultTest.setLastEditedDate(incomingTest.getLastEditedDate());
		resultTest.setCompletedDate(incomingTest.getCompletedDate());

		Address resultGMTAddress = resultTest.getGmtAddress();
		Address incomingGMTAddress = incomingTest.getGmtAddress();
		if (incomingGMTAddress == null) {
			resultTest.setGmtAddress(null);
		} else {
			if (resultGMTAddress == null) {
				resultGMTAddress = new Address();
				resultTest.setGmtAddress(resultGMTAddress);
			}
			this.mergeAddress(incomingGMTAddress, resultGMTAddress);
		}
	}

	/**
	 * Merges the IncomeTest Objects
	 *
	 * @param incoming
	 * @param onFile
	 * @return IncomeTest
	 * @throws ServiceException
	 */
	public IncomeTest mergeIncomeTest(IncomeTest incoming, IncomeTest onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A IncomeTest must not be null");
		Validate.notNull(onFile, "A IncomeTest must not be null");
		getCopyService().copy(incoming, onFile);

		// Remove deleted Income Test Statuses from targetIncomeTest
		removeDeletedIncomeTestStatuses(incoming, onFile);

		// Loop through and copy the test statuses
		Set statuses = incoming.getStatuses();
		Set onFileStatuses = onFile.getStatuses();

		for (Iterator iter = statuses.iterator(); iter.hasNext();) {
			IncomeTestStatus incomingStatus = (IncomeTestStatus) iter.next();

			IncomeTestStatus targetStatus = (IncomeTestStatus) matchRuleService
					.findMatchingElement(incomingStatus, onFileStatuses);

			if (targetStatus == null) {
				targetStatus = new IncomeTestStatus();
				onFile.addStatus(targetStatus);
			}
			mergeIncomeTestStatus(incomingStatus, targetStatus);
		}
		return onFile;
	}

	/**
	 * @param incomingStatus
	 * @param targetStatus
	 * @throws CopyServiceException
	 */
	public void mergeIncomeTestStatus(IncomeTestStatus incomingStatus,
			IncomeTestStatus targetStatus) throws CopyServiceException {
		Validate.notNull(incomingStatus,
				"A source IncomeTestStatus must not be null");
		Validate.notNull(targetStatus,
				"A target IncomeTestStatus must not be null");
		getCopyService().copy(incomingStatus, targetStatus);
	}

	/**
	 * Remove deleted income test statuses
	 *
	 * @param incoming
	 * @param onFile
	 */
	private void removeDeletedIncomeTestStatuses(IncomeTest incoming,
			IncomeTest onFile) {
		Set incomingStatuses = incoming.getStatuses();
		Set onFileStatuses = onFile.getStatuses();
		Set statusesToBeDeleted = new HashSet();

		// Remove all the deleted financials
		for (Iterator i = onFileStatuses.iterator(); i.hasNext();) {
			IncomeTestStatus incomeTestStatus = (IncomeTestStatus) i.next();
			if (matchRuleService.findMatchingElement(incomeTestStatus,
					incomingStatuses) == null) {
				// No match found remove
				statusesToBeDeleted.add(incomeTestStatus);
				if (logger.isDebugEnabled()) {
					logger.debug(" Removing IncomeTestStatus with id "
							+ incomeTestStatus.getEntityKey()
									.getKeyValueAsString());
				}
			}
		}
		for (Iterator i = statusesToBeDeleted.iterator(); i.hasNext();) {
			IncomeTestStatus incomeTestStatus = (IncomeTestStatus) i.next();
			onFile.removeStatus(incomeTestStatus);
		}
	}

	/**
	 * Merges the SpouseFinancials Objects
	 *
	 * @param incoming
	 * @param onFile
	 * @return SpouseFinancials
	 * @throws ServiceException
	 */
	public SpouseFinancials mergeSpouseFinancials(SpouseFinancials incoming,
			SpouseFinancials onFile) throws ServiceException {
		return this.mergeSpouseFinancials(incoming, onFile, false);
	}

	public SpouseFinancials mergeSpouseFinancials(SpouseFinancials incoming,
			SpouseFinancials onFile, boolean isMergeForZ07)
			throws ServiceException {
		Validate.notNull(incoming, "A SpouseFinancials must not be null");
		Validate.notNull(onFile, "A SpouseFinancials must not be null");

		// Perform merge only when something is changed
		if (AbstractEntity.matchesDomainValues(incoming, onFile)) {
			return onFile;
		}

		// Copy the specified fields
		getCopyService().copy(incoming, onFile);

		// Merge financial Information
		mergeFinancialInformation(incoming, onFile);

		// Merge Spouse Information
		Spouse incomingSpouse = incoming.getReportedOn();
		Spouse onFileSpouse = onFile.getReportedOn();
		if (incomingSpouse == null) {
			if (onFileSpouse != null)
				onFile.setReportedOn(null);
		} else {
			if (onFileSpouse == null) {
				onFileSpouse = new Spouse();
			}
			mergeSpouse(incomingSpouse, onFileSpouse, isMergeForZ07);
			onFile.setReportedOn(onFileSpouse);
		}

		return onFile;
	}

	/**
	 * Merges the DependentFinancials Objects
	 *
	 * @param incoming
	 * @param onFile
	 * @return DependentFinancials
	 * @throws ServiceException
	 */
	public DependentFinancials mergeDependentFinancials(
			DependentFinancials incoming, DependentFinancials onFile)
			throws ServiceException {
		return mergeDependentFinancials(incoming, onFile, false);
	}

	/**
	 * Merges the DependentFinancials Objects
	 *
	 * @param incoming
	 * @param onFile
	 * @param isMergeForZ07
	 * @return DependentFinancials
	 * @throws ServiceException
	 */
	public DependentFinancials mergeDependentFinancials(
			DependentFinancials incoming, DependentFinancials onFile,
			boolean isMergeForZ07) throws ServiceException {
		Validate.notNull(incoming, "A DependentFinancials must not be null");
		Validate.notNull(onFile, "A DependentFinancials must not be null");

		// Perform merge only when something is changed
		if (AbstractEntity.matchesDomainValues(incoming, onFile)) {
			return onFile;
		}

		// Copy the specified fields
		getCopyService().copy(incoming, onFile);

		// Merge financial Information
		mergeFinancialInformation(incoming, onFile);

		Dependent incomingDependent = incoming.getReportedOn();
		Dependent onFileDependent = onFile.getReportedOn();
		if (incomingDependent == null) {
			if (onFileDependent != null)
				onFile.setReportedOn(null);
		} else {
			if (onFileDependent == null) {
				onFileDependent = new Dependent();
			}
			onFile.setReportedOn(onFileDependent);
			mergeDependent(incomingDependent, onFileDependent, isMergeForZ07);
		}

		return onFile;
	}

	/**
	 * Merges the Spouse Objects
	 *
	 * @param incoming
	 * @param onFile
	 * @return Spouse
	 * @throws ServiceException
	 */

	public Spouse mergeSpouse(Spouse incoming, Spouse onFile)
			throws ServiceException {
		return this.mergeSpouse(incoming, onFile, false);
	}

	public Spouse mergeSpouse(Spouse incoming, Spouse onFile,
			boolean isMergeForZ07) throws ServiceException {

		Validate.notNull(incoming, "A Spouse must not be null");
		Validate.notNull(onFile, "A Spouse must not be null");
		getCopyService().copy(incoming, onFile);
		mergeSSN(incoming, onFile, isMergeForZ07);

		// CCR 10140 -- update spouse address/phone is now done after
		// acceptFinancialStatement, using ilog rules. Employment is merged here
		// first, and then a post processing is done after
		// acceptFinancialStatement.

		// merge new ESR 3.1 VOA fields - CodeCR7423
		// ESR 3.5_CodeCR10535 Receiving Spouse Employment from Z07 is broken, bypass employment merge
		// for spouse when data from Z07 since rules are supposed to do it.
		if (!isMergeForZ07) {
			mergeSpouseEmployment(incoming, onFile);
		}
		// end of CodeCR7423

		return onFile;
	}

	public void mergeSpouseEmployment(Spouse incoming, Spouse onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An incoming spouse must not be null ");
		Validate.notNull(onFile, "An onFile spouse must not be null ");
		Employment incomingEM = incoming.getEmployment();
		Employment onFileEM = onFile.getEmployment();

		if (incomingEM != null) {
			if (onFileEM == null) {
				onFileEM = new Employment();
			}
			this.mergeEmployment(incomingEM, onFileEM);
			onFile.setEmployment(onFileEM);
		} else {
			onFile.setEmployment(null);
		}
	}

	/**
	 * Merges the Spouse Address
	 *
	 * @param incoming
	 * @param onFile
	 * @throws ServiceException
	 */
	public void mergeSpouseAddress(Spouse incoming, Spouse onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming spouse must not be null ");
		Validate.notNull(onFile, "An onFile spouse must not be null ");

		Address incomingAddresses = incoming.getAddress();
		Address onfileAddress = onFile.getAddress();
		if (onfileAddress == null) {
			onfileAddress = new Address();
		}
		if (incomingAddresses != null) {
			mergeAddress(incomingAddresses, onfileAddress);
			onFile.setAddress(onfileAddress);
		} else {
			onFile.setAddress(null);
		}
	}

	public void mergeSpouseHomePhone(Spouse incoming, Spouse onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming spouse must not be null ");
		Validate.notNull(onFile, "An onFile spouse must not be null ");

		Phone incomingPhone = incoming.getHomePhone();
		Phone onfilePhone = onFile.getHomePhone();
		if (onfilePhone == null) {
			onfilePhone = new Phone();
		}
		if (incomingPhone != null) {
			mergePhone(incomingPhone, onfilePhone);
			onFile.setHomePhone(onfilePhone);
		} else {
			onFile.setHomePhone(null);
		}

	}

	public void mergeSSN(Relation incoming, Relation onFile)
			throws ServiceException {
		this.mergeSSN(incoming, onFile, false);
	}

	public void mergeSSN(Relation incoming, Relation onFile,
			boolean isMergeForZ07) throws ServiceException {
		SSN prevSsn = onFile.getOfficialSsn() == null ? null : (SSN) onFile
				.getOfficialSsn().clone();

		Set ssns = incoming.getSsns();
		if (ssns != null && ssns.size() > 0) {
			Set merged = null;
			if (isMergeForZ07) {
				merged = this.mergeSsnSets(ssns, onFile.getSsns());
			} else {
				merged = this.mergeSets(ssns, onFile.getSsns());
			}
			onFile.removeAllSsns();
			for (Iterator iter = merged.iterator(); iter.hasNext();) {
				SSN ssn = (SSN) iter.next();
				onFile.addSsn(ssn);
			}
		} else {
			onFile.removeAllSsns();
		}

		SSN onFileSsn = onFile.getOfficialSsn();
		if (onFileSsn != null && onFileSsn.getSsnText() != null
				&& !SSN.isValidSSN(onFileSsn.getSsnText())) {

			// revert the ssn back to previous
			if (prevSsn != null) {
				getCopyService().copy(prevSsn, onFileSsn);
			} else {
				onFile.removeSsn(onFileSsn);
			}
		}
		// if no ssn is determined - pseudo ssn is generated but its in
		// FinancialsHelperService
		// so that service can also call that method directly in cases where
		// dependent is
		// modified on a dep screen and no rules are processed.
	}

	/**
	 * Merges the Dependent Objects
	 *
	 * @param incoming
	 * @param onFile
	 * @return Dependent
	 * @throws ServiceException
	 */
	public Dependent mergeDependent(Dependent incoming, Dependent onFile)
			throws ServiceException {
		return mergeDependent(incoming, onFile, false);
	}

	/**
	 * Merges the Dependent Objects
	 *
	 * @param incoming
	 * @param onFile
	 * @param isMergeForZ07
	 * @return Dependent
	 * @throws ServiceException
	 */
	public Dependent mergeDependent(Dependent incoming, Dependent onFile,
			boolean isMergeForZ07) throws ServiceException {
		Validate.notNull(incoming, "A Dependent must not be null");
		Validate.notNull(onFile, "A Dependent must not be null");
		getCopyService().copy(incoming, onFile);
		mergeSSN(incoming, onFile, isMergeForZ07);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeBeneficiaryTravel(gov.va.med.esr.common.model.financials.BeneficiaryTravel,
	 *      gov.va.med.esr.common.model.financials.BeneficiaryTravel)
	 */
	public BeneficiaryTravel mergeBeneficiaryTravel(BeneficiaryTravel incoming,
			BeneficiaryTravel onFile) throws ServiceException {
		Validate.notNull(incoming, "A BeneficiaryTravel must not be null");
		Validate.notNull(onFile, "A BeneficiaryTravel must not be null");
		getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeHardship(gov.va.med.esr.common.model.financials.Hardship,
	 *      gov.va.med.esr.common.model.financials.Hardship)
	 */
	public Hardship mergeHardship(Hardship incoming, Hardship onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A Hardship must not be null");
		Validate.notNull(onFile, "A Hardship must not be null");
		getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergePatientVisitSummary(gov.va.med.esr.common.model.financials.PatientVisitSummary,
	 *      gov.va.med.esr.common.model.financials.PatientVisitSummary)
	 */
	public PatientVisitSummary mergePatientVisitSummary(
			PatientVisitSummary incoming, PatientVisitSummary onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A PatientVisitSummary must not be null");
		Validate.notNull(onFile, "A PatientVisitSummary must not be null");
		getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeFeeBasis(gov.va.med.esr.common.model.ee.FeeBasis,
	 *      gov.va.med.esr.common.model.ee.FeeBasis)
	 */
	public FeeBasis mergeFeeBasis(FeeBasis incoming, FeeBasis onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A incoming FeeBasis must not be null");
		Validate.notNull(onFile, "A FeeBasis on file must not be null");
		getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeReceivedEnrollment(gov.va.med.esr.common.model.ee.ReceivedEnrollment,
	 *      gov.va.med.esr.common.model.ee.ReceivedEnrollment)
	 */
	public ReceivedEnrollment mergeReceivedEnrollment(
			ReceivedEnrollment incoming, ReceivedEnrollment onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A Received Enrollment must not be null");
		Validate.notNull(onFile, "A Received Enrollment must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeReceivedEligibility(gov.va.med.esr.common.model.ee.ReceivedEligibility,
	 *      gov.va.med.esr.common.model.ee.ReceivedEligibility)
	 */
	public ReceivedEligibility mergeReceivedEligibility(
			ReceivedEligibility incoming, ReceivedEligibility onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A Received Eligibility must not be null");
		Validate.notNull(onFile, "A Received Eligibility must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeEnrollmentOverride(gov.va.med.esr.common.model.ee.EnrollmentOverride,
	 *      gov.va.med.esr.common.model.ee.EnrollmentOverride)
	 */
	public EnrollmentOverride mergeEnrollmentOverride(
			EnrollmentOverride incoming, EnrollmentOverride onFile)
			throws ServiceException {
		Validate.notNull(incoming,
				"A received Enrollment Override must not be null");
		Validate.notNull(onFile,
				"An on file Enrollment Override must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * merge CDDiagnosis
	 */
	public CDDiagnosis mergeCDDiagnosis(CDDiagnosis incoming, CDDiagnosis onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A CDDiagnosis must not be null");
		Validate.notNull(onFile, "A CDDiagnosis on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * merge CDProcedure
	 */
	public CDProcedure mergeCDProcedure(CDProcedure incoming, CDProcedure onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A CDProcedure must not be null");
		Validate.notNull(onFile, "A CDProcedure on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * merge CDCondition
	 */
	public CDCondition mergeCDCondition(CDCondition incoming, CDCondition onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A CDCondition must not be null");
		Validate.notNull(onFile, "A CDCondition on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * Merge Death Record
	 */
	public DeathRecord mergeDeathRecord(DeathRecord incoming, DeathRecord onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A DeathRecord must not be null");
		Validate.notNull(onFile, "A DeathRecord on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * Merge Eligibility
	 */
	public Eligibility mergeEligibility(Eligibility incoming, Eligibility onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A Eligibility must not be null");
		Validate.notNull(onFile, "A Eligibility on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * Merge Enrollment Determination TODO changes may be required as
	 * eligibilities can move from sec to primary
	 */
	public EnrollmentDetermination mergeEnrollmentDetermination(
			EnrollmentDetermination incoming, EnrollmentDetermination onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A EnrollmentDetermination must not be null");
		Validate.notNull(onFile,
				"A EnrollmentDetermination on file must not be null");


		boolean matchedEnrollment = AbstractEntity.matchesDomainConcept(incoming, onFile);

		// merge primary eligibility
		Eligibility incomingPrimary = incoming.getPrimaryEligibility();
		Eligibility onFileprimary = onFile.getPrimaryEligibility();
		//  CCR 11438 - avoid update if no change
		boolean matchedPrimary = AbstractEntity.matchesDomainConcept(incomingPrimary, onFileprimary);

		boolean matchedSecondaries = true;
		if (!ObjectUtils.equals(incoming.getSecondaryEligibilities().size(), onFile.getSecondaryEligibilities().size())) {
			matchedSecondaries = false;
		}
		else {
			for (Iterator iter = incoming.getSecondaryEligibilities().iterator(); iter.hasNext();) {
				Eligibility e = (Eligibility) iter.next();
				Eligibility match = (Eligibility)this.getMatchRuleService().findMatchingElement(e, onFile.getSecondaryEligibilities());
				if (match == null) {
					matchedSecondaries = false;
					break;
				}
			}
		}

		boolean matchedOthers = true;
		if (!ObjectUtils.equals(incoming.getOtherEligibilities().size(), onFile.getOtherEligibilities().size())) {
			matchedOthers = false;
		}
		else {
			// size matched so look for 100% business matches
			for (Iterator iter = incoming.getOtherEligibilities().iterator(); iter.hasNext();) {
				Eligibility e = (Eligibility) iter.next();
				Eligibility match = (Eligibility)this.getMatchRuleService().findMatchingElement(e, onFile.getOtherEligibilities());
				if (match == null) {
					matchedOthers = false;
					break;
				}
			}
		}

		//  CCR 11438 - avoid update if no change
		// Merge only when something did not match
		if (!matchedEnrollment || !matchedPrimary ||
				!matchedOthers || !matchedSecondaries) {
			// Use copy to copy lookups and status data
			getCopyService().copy(incoming, onFile);

			// merge primary eligibility
			if (incomingPrimary != null) {
				if (onFileprimary == null) {
					onFileprimary = new Eligibility();
					onFile.setPrimaryEligiblity(onFileprimary);
				}
				getCopyService().copy(incomingPrimary, onFileprimary);

			} else {
				onFile.setPrimaryEligiblity(null);
			}

			// merge secondary eligibilities
			Set mergedSecondaryEligibilities = mergeSets(
					incoming.getSecondaryEligibilities(),
					onFile.getSecondaryEligibilities());
			// Remove all the secondary eligibilities and add merged set
			Set secondaryEligibilities = new HashSet(onFile.getSecondaryEligibilities());
			for (Iterator iter = secondaryEligibilities.iterator(); iter.hasNext();) {
				onFile.removeSecondaryEligibility((Eligibility) iter.next());
			}
			for (Iterator iter = mergedSecondaryEligibilities.iterator(); iter
			.hasNext();) {
				onFile.addSecondaryEligibility((Eligibility) iter.next());
			}
			// merge other eligibilities
			Set mergedOtherEligibilities = mergeSets(
					incoming.getOtherEligibilities(),
					onFile.getOtherEligibilities());
			// Remove all the secondary eligibilities and add merged set
			Set otherEligibilities = new HashSet(onFile.getOtherEligibilities());

			for (Iterator iter = otherEligibilities.iterator(); iter.hasNext();) {
				onFile.removeOtherEligibility((Eligibility) iter.next());
			}
			for (Iterator iter = mergedOtherEligibilities.iterator(); iter
			.hasNext();) {
				onFile.addOtherEligibility((Eligibility) iter.next());
			}
		}

		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergePrisonerOfWar(gov.va.med.esr.common.model.ee.PrisonerOfWar,
	 *      gov.va.med.esr.common.model.ee.PrisonerOfWar)
	 */
	public PrisonerOfWar mergePrisonerOfWar(PrisonerOfWar incoming,
			PrisonerOfWar onFile) throws ServiceException {
		Validate.notNull(incoming, "A PrisonerOfWar must not be null");
		Validate.notNull(onFile, "A PrisonerOfWar on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergePOWEpisode(gov.va.med.esr.common.model.ee.POWEpisode,
	 *      gov.va.med.esr.common.model.ee.POWEpisode)
	 */
	public POWEpisode mergePOWEpisode(POWEpisode incoming, POWEpisode onFile)
			throws ServiceException {
		Validate.notNull(incoming, "A POWEpisode must not be null");
		Validate.notNull(onFile, "A POWEpisode on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergePrisonerOfWar(gov.va.med.esr.common.model.ee.PrisonerOfWar,
	 *      gov.va.med.esr.common.model.person.Person)
	 */
	public PrisonerOfWar mergePrisonerOfWar(PrisonerOfWar incoming,
			Person onFile) throws ServiceException {
		Validate.notNull(onFile, "A person on file must not be null");

		if (incoming == null) {
			onFile.setPrisonerOfWar(null);
			return null;
		}

		PrisonerOfWar onFilePow = onFile.getPrisonerOfWar();

		if (onFilePow == null) {
			onFilePow = new PrisonerOfWar();
			onFile.setPrisonerOfWar(onFilePow);
		}

		// Use copy to copy lookups and data
		this.mergePrisonerOfWar(incoming, onFilePow);

		// Merge episodes
		Set mergedEpisodes = mergeSets(incoming.getEpisodes(),
				onFilePow.getEpisodes());

		// Remove all the episodes and add merged set
		onFilePow.removeAllEpisodes();

		for (Iterator iter = mergedEpisodes.iterator(); iter.hasNext();) {
			onFilePow.addEpisode((POWEpisode) iter.next());
		}

		return onFile.getPrisonerOfWar();
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeSHAD(gov.va.med.esr.common.model.ee.SHAD,
	 *      gov.va.med.esr.common.model.person.Person)
	 */
	public SHAD mergeSHAD(SHAD incoming, Person onFile) throws ServiceException {
		Validate.notNull(onFile, "A person on file must not be null");

		if (incoming == null) {
			onFile.setShad(null);
			return null;
		}

		SHAD onFileSHAD = onFile.getShad();

		if (onFileSHAD == null) {
			onFileSHAD = new SHAD();
			onFile.setShad(onFileSHAD);
		}

		// Use copy to copy lookups and data
		getCopyService().copy(incoming, onFileSHAD);

		// Merge documents
		Set mergedDocuments = mergeSets(incoming.getDocuments(),
				onFileSHAD.getDocuments());

		// Remove all the documents and add merged set
		onFileSHAD.removeAllDocuments();

		if (mergedDocuments.size() > 0) {
			onFileSHAD.addAllDocuments(mergedDocuments);
		}

		RegistryTrait trait = incoming.getRegistryTrait();
		RegistryTrait onFileTrait = onFileSHAD.getRegistryTrait();
		if (trait == null) {
			onFileSHAD.setRegistryTrait(null);
		} else {
			if (onFileTrait == null) {
				onFileTrait = new RegistryTrait();
				onFileSHAD.setRegistryTrait(onFileTrait);
			}
			getCopyService().copy(trait, onFileTrait);
			Set mergedDetails = mergeSets(trait.getRegistryTraitDetails(),
					onFileTrait.getRegistryTraitDetails());
			onFileTrait.removeAllRegistryTraitDetails();
			if (mergedDetails.size() > 0) {
				onFileTrait.addAllRegistryTraitDetails(mergedDetails);
			}
		}

		return onFile.getShad();
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeSignatureImage(gov.va.med.esr.common.model.person.SignatureImage,
	 *      gov.va.med.esr.common.model.person.Person)
	 */
	public SignatureImage mergeSignatureImage(SignatureImage incoming,
			Person onFile) throws ServiceException {
		Validate.notNull(incoming, "A SignatureImage must not be null");
		Validate.notNull(onFile, "A person on file must not be null");

		Set signatures = onFile.getSignatureImages();

		// If a match is not found, this will result in a new record
		SignatureImage match = incoming;
		boolean found = false;

		for (Iterator i = signatures.iterator(); i.hasNext();) {
			// Looking for a match. If found, update it with
			// data from an in-coming SignatureImage
			AbstractEntity obj = (AbstractEntity) i.next();
			if (this.getMatchRuleService()
					.match((AbstractEntity) obj, incoming)) {
				match = (SignatureImage) obj;
				this.mergeSignatureImage(incoming, match);
				onFile.addSignatureImage(match);
				found = true;
				break;
			}
		}

		// Create new record if not matched
		if (!found) {
			SignatureImage signatureImage = new SignatureImage();
			this.mergeSignatureImage(match, signatureImage);
			onFile.addSignatureImage(signatureImage);
		}

		return match;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeSignatureImage(gov.va.med.esr.common.model.person.SignatureImage,
	 *      gov.va.med.esr.common.model.person.SignatureImage)
	 */
	public SignatureImage mergeSignatureImage(SignatureImage incoming,
			SignatureImage onFile) throws ServiceException {
		Validate.notNull(incoming, "A SignatureImage must not be null");
		Validate.notNull(onFile, "A SignatureImage on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeRegistry
	 */
	public PurpleHeart mergeRegistry(PurpleHeart incoming, PurpleHeart onFile)
			throws ServiceException {
		return this.mergeRegistry(incoming, onFile, true);
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeRegistry(gov.va.med.esr.common.model.ee.PrisonerOfWar,
	 *      gov.va.med.esr.common.model.ee.PrisonerOfWar)
	 */
	public PrisonerOfWar mergeRegistry(PrisonerOfWar incoming,
			PrisonerOfWar onFile) throws ServiceException {
		return this.mergeRegistry(incoming, onFile, true);
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeRegistry(gov.va.med.esr.common.model.ee.SHADRegistry,
	 *      gov.va.med.esr.common.model.ee.SHADRegistry)
	 */
	public SHAD mergeRegistry(SHAD incoming, SHAD onFile)
			throws ServiceException {
		return this.mergeRegistry(incoming, onFile, true);
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeRegistry
	 */
	protected RegistryTrait mergeRegistryTrait(RegistryTrait incoming,
			RegistryTrait onFile) throws ServiceException {
		Validate.notNull(incoming, "A RegistryTrait must not be null");
		Validate.notNull(onFile, "A RegistryTrait on file must not be null");
		try {
			// Copy RegistryTraits.
			this.getCopyService().copy(incoming, onFile);

			// Remove deleted registry trait details
			Set onFileDetails = new HashSet(onFile.getRegistryTraitDetails());
			for (Iterator iter = onFileDetails.iterator(); iter.hasNext();) {
				RegistryTraitDetail detail = (RegistryTraitDetail) iter.next();
				if (incoming.getRegistryTraitDetailByEntityKey(detail
						.getEntityKey()) == null) {
					onFile.removeRegistryTraitDetail(detail);
				}
			}

			// Add/update registry trait detail
			for (Iterator iter = incoming.getRegistryTraitDetails().iterator(); iter
					.hasNext();) {
				RegistryTraitDetail incomingDetail = (RegistryTraitDetail) iter
						.next();
				EntityKey key = incomingDetail.getEntityKey();
				if (key == null) {
					// Newly added registry trait detail, just add it
					RegistryTraitDetail detail = new RegistryTraitDetail();
					detail.setMilitaryServiceNumber(incomingDetail
							.getMilitaryServiceNumber());
					onFile.addRegistryTraitDetail(detail);
				} else {
					// Updated registry trait detail, merge it
					RegistryTraitDetail onFiledetail = onFile
							.getRegistryTraitDetailByEntityKey(key);
					onFiledetail.setMilitaryServiceNumber(incomingDetail
							.getMilitaryServiceNumber());
				}
			}

		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge an RegistryTrait", ex);
			throw new RuntimeException("Failed to merge an RegistryTrait", ex);
		}
		return onFile;
	}

	/**
	 * This merge service should only used where you don't need to apply rules
	 * prior to accepting data.
	 *
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergePerson(gov.va.med.esr.common.model.person.Person,
	 *      gov.va.med.esr.common.model.person.Person, boolean)
	 */
	public Person mergePerson(Person incoming, Person onFile,
			boolean isUpdateFromGUI) throws ServiceException {
		Validate.notNull(incoming, "An incoming personR must not be null");
		Validate.notNull(onFile, "A person on file must not be null");

		this.mergeDemographic(incoming, onFile, isUpdateFromGUI);
		this.mergeIdentityTraits(incoming, onFile, true); // copy gender, dob,
															// legal name
		this.mergeDeathRecord(incoming, onFile);
		this.mergeSSN(incoming, onFile);
		this.mergePatientVisitSummary(incoming, onFile);
		this.mergeSHAD(incoming.getShad(), onFile);

		// Merge E&E data
		this.mergeEligibilityVerification(
				incoming.getEligibilityVerification(), onFile);
		this.mergeServiceConnectionAward(incoming.getServiceConnectionAward(),
				onFile);
		this.mergeCancelDecline(incoming.getCancelDecline(), onFile);
		this.mergeIncompetenceRuling(incoming.getIncompetenceRuling(), onFile);
		this.mergeIneligibilityFactor(incoming.getIneligibilityFactor(), onFile);
		this.mergeSpecialFactor(incoming, onFile);
		this.mergeMonetaryBenefitAward(incoming.getMonetaryBenefitAward(),
				onFile);
		this.mergeMedicaidFactor(incoming, onFile);
		this.mergeMilitaryService(incoming, onFile, isUpdateFromGUI);
		this.mergeFeeBasis(incoming, onFile);
		this.mergeReceivedEligibility(incoming, onFile);
		this.mergePrisonerOfWar(incoming.getPrisonerOfWar(), onFile);
		this.mergePurpleHeart(incoming, onFile);
		this.mergeAssociation(incoming, onFile);
		this.mergeAddress(incoming, onFile);
		this.mergePhone(incoming, onFile);
		this.mergeEmail(incoming, onFile);
		this.mergeInsurancePolicy(incoming, onFile);
		this.mergeEnrollmentOverride(incoming, onFile);
		this.mergeSignatureImage(incoming, onFile);
		this.mergeCatastrophicDisability(incoming, onFile);
		this.mergeMilitarySexualTrauma(incoming, onFile);
		this.mergeNoseThroatRadium(incoming, onFile);
		this.mergeFinancialData(incoming, onFile);
		// Fix for CR_7565 adding methd to merge Emergency Response Incicator
		this.mergeEmergencyResponseIndicator(incoming, onFile);

		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeEmergencyResponseIndicator(gov.va.med.esr.common.model.person.EmergencyResponseIndicator,
	 *      gov.va.med.esr.common.model.person.EmergencyResponseIndicator)
	 */
	public EmergencyResponseIndicator mergeEmergencyResponseIndicator(
			EmergencyResponseIndicator incoming,
			EmergencyResponseIndicator onFile) throws ServiceException {
		Validate.notNull(incoming,
				"An incoming EmergencyResponseIndicator must not be null");
		Validate.notNull(onFile,
				"A EmergencyResponseIndicator on file must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeEmergencyResponseIndicator(gov.va.med.esr.common.model.person.Person,
	 *      gov.va.med.esr.common.model.person.Person)
	 */
	public void mergeEmergencyResponseIndicator(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An incoming personR must not be null");
		Validate.notNull(onFile, "A person on file must not be null");

		Set merged = this.mergeSets(incoming.getEmergencyResponseIndicators(),
				onFile.getEmergencyResponseIndicators());
		// Remove all the documents and add merged set
		onFile.removeAllEmergencyResponseIndicators();

		for (Iterator iter = merged.iterator(); iter.hasNext();) {
			onFile.addEmergencyResponseIndicator((EmergencyResponseIndicator) iter
					.next());
		}
	}

	/*
	 * @see
	 * gov.va.med.esr.common.rule.service.MergeRuleService#mergeEGTSetting(gov
	 * .va.med.esr.common.model.ee.EGTSetting,
	 * gov.va.med.esr.common.model.ee.EGTSetting)
	 */
	public EGTSetting mergeEGTSetting(EGTSetting incoming, EGTSetting onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An incoming EGTSetting must not be null");
		Validate.notNull(onFile, "A EGTSetting on file must not be null");
		try {
			// Copy EGTSettings.
			this.getCopyService().copy(incoming, onFile);
		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge an EGTSetting", ex);
			throw new RuntimeException("Failed to merge an EGTSetting", ex);
		}
		return onFile;
	}

	public EGTProcessStatistic mergeEGTProcessStatistic(
			EGTProcessStatistic incoming, EGTProcessStatistic onFile)
			throws ServiceException {
		Validate.notNull(incoming,
				"An incoming EGTProcessStatistic must not be null");
		Validate.notNull(onFile,
				"A EGTProcessStatistic on file must not be null");
		try {
			// Copy EGTSettings.
			this.getCopyService().copy(incoming, onFile);
		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge an EGTProcessStatistic", ex);
			throw new RuntimeException(
					"Failed to merge an EGTProcessStatistic", ex);
		}
		return onFile;
	}

	public EGTSiteStatistic mergeEGTSiteStatistic(EGTSiteStatistic incoming,
			EGTSiteStatistic onFile) throws ServiceException {
		Validate.notNull(incoming,
				"An incoming EGTSiteStatistic must not be null");
		Validate.notNull(onFile, "A EGTSiteStatistic on file must not be null");
		try {
			// Copy EGTSettings.
			this.getCopyService().copy(incoming, onFile);
		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge an EGTSiteStatistic", ex);
			throw new RuntimeException("Failed to merge an EGTSiteStatistic",
					ex);
		}
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeRegistry(gov.va.med.esr.common.model.ee.PrisonerOfWar,
	 *      gov.va.med.esr.common.model.ee.PrisonerOfWar, boolean)
	 */
	public PrisonerOfWar mergeRegistry(PrisonerOfWar incoming,
			PrisonerOfWar onFile, boolean isUI) throws ServiceException {
		Validate.notNull(incoming, "A Registry must not be null");
		Validate.notNull(onFile, "A Registry on file must not be null");
		try {

			// Copy registry record properties except the RegistryTrait and
			// Episodes.
			this.getCopyService().copy(incoming, onFile);

			// Copy registryTrait properties
			this.mergeRegistryTrait(incoming.getRegistryTrait(),
					onFile.getRegistryTrait());

			if (isUI) {
				// Remove deleted Episodes
				Set onFileEpisodes = new HashSet(onFile.getEpisodes());
				for (Iterator iter = onFileEpisodes.iterator(); iter.hasNext();) {
					POWEpisode episode = (POWEpisode) iter.next();
					if (incoming.getEpisodeByEntityKey(episode.getEntityKey()) == null) {
						onFile.removeEpisode(episode);
					}
				}
			}

			// Add/update Episodes
			for (Iterator iter = incoming.getEpisodes().iterator(); iter
					.hasNext();) {
				POWEpisode incomingEpisode = (POWEpisode) iter.next();
				EntityKey key = incomingEpisode.getEntityKey();
				if (key == null) {
					// Newly added episode, just add it
					POWEpisode episode = new POWEpisode();
					this.getCopyService().copy(incomingEpisode, episode);
					onFile.addEpisode(episode);
				} else {
					// Updated episode, merge it
					POWEpisode onFileEpisode = onFile
							.getEpisodeByEntityKey(key);
					this.getCopyService().copy(incomingEpisode, onFileEpisode);
				}
			}
		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge an POW registry", ex);
			throw new RuntimeException("Failed to merge an POW registry", ex);
		}
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeRegistry(gov.va.med.esr.common.model.ee.PurpleHeart,
	 *      gov.va.med.esr.common.model.ee.PurpleHeart, boolean)
	 */
	public PurpleHeart mergeRegistry(PurpleHeart incoming, PurpleHeart onFile,
			boolean isUI) throws ServiceException {
		Validate.notNull(incoming, "A Registry must not be null");
		Validate.notNull(onFile, "A Registry on file must not be null");
		try {

			// Copy registry record properties except the RegistryTrait and
			// Documents.
			this.getCopyService().copy(incoming, onFile);

			// Copy registryTrait properties
			this.mergeRegistryTrait(incoming.getRegistryTrait(),
					onFile.getRegistryTrait());

			if (isUI) {
				// Remove deleted Documents
				Set onFileDocs = new HashSet(onFile.getDocuments());
				for (Iterator iter = onFileDocs.iterator(); iter.hasNext();) {
					PurpleHeartDocument doc = (PurpleHeartDocument) iter.next();
					if (incoming.getDocumentByEntityKey(doc.getEntityKey()) == null) {
						onFile.removeDocument(doc);
					}
				}
			}

			// Add/update Documents
			for (Iterator iter = incoming.getDocuments().iterator(); iter
					.hasNext();) {
				PurpleHeartDocument incomingDoc = (PurpleHeartDocument) iter
						.next();
				EntityKey key = incomingDoc.getEntityKey();
				if (key == null) {
					// Newly added document, just add it
					PurpleHeartDocument doc = new PurpleHeartDocument();
					this.getCopyService().copy(incomingDoc, doc);
					onFile.addDocument(doc);
				} else {
					// Updated document, merge it
					PurpleHeartDocument onFileDoc = onFile
							.getDocumentByEntityKey(key);
					this.getCopyService().copy(incomingDoc, onFileDoc);
				}
			}
		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge an PurpleHeart registry", ex);
			throw new RuntimeException(
					"Failed to merge an PurpleHeart registry", ex);
		}
		return onFile;
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeRegistry(gov.va.med.esr.common.model.ee.SHADRegistry,
	 *      gov.va.med.esr.common.model.ee.SHADRegistry, boolean)
	 */
	public SHAD mergeRegistry(SHAD incoming, SHAD onFile, boolean isUI)
			throws ServiceException {
		Validate.notNull(incoming, "A Registry must not be null");
		Validate.notNull(onFile, "A Registry on file must not be null");
		try {

			// Copy registry record properties except the RegistryTrait and
			// Documents.
			this.getCopyService().copy(incoming, onFile);

			// Copy registryTrait properties
			this.mergeRegistryTrait(incoming.getRegistryTrait(),
					onFile.getRegistryTrait());

			if (isUI) {
				// Remove deleted Documents
				Set onFileDocs = new HashSet(onFile.getDocuments());
				for (Iterator iter = onFileDocs.iterator(); iter.hasNext();) {
					SHADDocument doc = (SHADDocument) iter.next();
					if (incoming.getDocumentByEntityKey(doc.getEntityKey()) == null) {
						onFile.removeDocument(doc);
					}
				}
			}

			// Add/update Documents
			for (Iterator iter = incoming.getDocuments().iterator(); iter
					.hasNext();) {
				SHADDocument incomingDoc = (SHADDocument) iter.next();
				EntityKey key = incomingDoc.getEntityKey();
				if (key == null) {
					// Newly added document, just add it
					SHADDocument doc = new SHADDocument();
					this.getCopyService().copy(incomingDoc, doc);
					onFile.addDocument(doc);
				} else {
					// Updated document, merge it
					SHADDocument onFileDoc = onFile.getDocumentByEntityKey(key);
					this.getCopyService().copy(incomingDoc, onFileDoc);
				}
			}
		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge an SHAD registry", ex);
			throw new RuntimeException("Failed to merge an SHAD registry", ex);
		}
		return onFile;
	}

	/**
	 * Fix for CR_8881 as of now vista is not sending ssaVerificationStatus for
	 * spouse in Z07, so dont update the ssaVerificationStaus in the ESR.
	 * */
	public Set mergeSsnSets(Set incoming, Set onFile) throws ServiceException {
		Set mergedSet = new HashSet();

		Validate.notNull(incoming, "A incoming set must not be null");
		Validate.notNull(onFile, "A onFile set must not be null");

		for (Iterator i = incoming.iterator(); i.hasNext();) {
			AbstractEntity incomingObj = (AbstractEntity) i.next();
			boolean matchFound = false;
			// Find the matching element from the onFile Set
			for (Iterator j = onFile.iterator(); j.hasNext();) {
				AbstractEntity onFileObj = (AbstractEntity) j.next();
				if (getMatchRuleService().match(incomingObj, onFileObj)) {
					// match found merge the items
					SSN onFileSsn = (SSN) onFileObj;
					if (onFileSsn.getSsaVerificationStatus() == null
							|| // CCR9925: Failed to invoke rule
								// ProcessFinancialInfo. This is due to null
								// pointer
							!(onFileSsn.getSsaVerificationStatus().getCode()
									.equalsIgnoreCase(SSAVerificationStatus.NEW_RECORD
											.getCode()))) {
						SSAVerificationStatus status = onFileSsn
								.getSsaVerificationStatus();
						Date date = onFileSsn.getSsaVerificationDate();
						Date sentDate = onFileSsn.getSsaSentDate();
						Date receivedDate = onFileSsn.getSsaReceivedDate();
						SSAMessage message = onFileSsn.getSsaMessage();
						getCopyService().copy(incomingObj, onFileObj);
						onFileSsn.setSsaMessage(message);
						onFileSsn.setSsaReceivedDate(receivedDate);
						onFileSsn.setSsaSentDate(sentDate);
						onFileSsn.setSsaVerificationDate(date);
						onFileSsn.setSsaVerificationStatus(status);
						mergedSet.add(onFileObj);
					} else {
						getCopyService().copy(incomingObj, onFileObj);
						mergedSet.add(onFileObj);
					}
					matchFound = true;
					break;
				}
			}
			// If no match found create a new instance and merge
			if (!matchFound) {
				try {
					AbstractEntity onFileObj = (AbstractEntity) incomingObj
							.getClass().newInstance();
					getCopyService().copy(incomingObj, onFileObj);
					mergedSet.add(onFileObj);
				} catch (Exception e) {
					throw new CopyServiceException(
							"Unable to create new insatnce for "
									+ incomingObj.getClass().getName());
				}
			}
		}
		// Return the merged set
		return mergedSet;
	}

	public Set mergeSets(Set incoming, Set onFile) throws ServiceException {
		return mergeSets(incoming, onFile, false);
	}

	public Set mergeSets(Set incoming, Set onFile,
			boolean alwaysAddIncomingDuplicates) throws ServiceException {
		Validate.notNull(incoming, "The incoming set must not be null");

		Validate.notNull(onFile, "The onFile set must not be null");

		Set mergedSet = new HashSet();
		Set alreadyProcessedIncomingItems = new HashSet();

		/* For all the incoming items */
		for (Iterator i = incoming.iterator(); i.hasNext();) {
			AbstractEntity incomingObj = (AbstractEntity) i.next();

			/*
			 * If we want to blindly include all duplicate incoming items
			 * (comparing items using the matchRuleService, which compares the
			 * items' "domain concepts")
			 */
			if (alwaysAddIncomingDuplicates) {
				/* Search for a previous duplicate item */
				AbstractEntity testDuplicate = getMatchingObjectInSet(
						incomingObj, alreadyProcessedIncomingItems);
				if (testDuplicate != null) {
					/*
					 * If we found a previously processed item that matches,
					 * then blindly add it by creating a new instance,
					 * populating it using the copyService and adding it to our
					 * result mergedSet. Continue to next incoming item.
					 */
					createNewInstanceAndMerge(mergedSet, incomingObj);
					continue;
				} else {
					/*
					 * We didn't find a previous duplicate, so add it to the
					 * duplicates list for future searches and continue to main
					 * matching logic below.
					 */
					alreadyProcessedIncomingItems.add(incomingObj);
				}
			}

			/*
			 * Search for an existing element that matches (using the
			 * "domain concept" as criteria) in the onFile Set
			 */
			AbstractEntity matchingObj = getMatchingObjectInSet(incomingObj,
					onFile);

			if (matchingObj != null) {
				/*
				 * match found, so merge the new item into the existing one and
				 * add to the result mergedSet. Note that this actually modifies
				 * the onFile item; a better idea would be to clone the onFile
				 * item first so we don't change the original set. But I guess
				 * we discard that set later so it shouldn't matter.
				 */
				getCopyService().copy(incomingObj, matchingObj);
				mergedSet.add(matchingObj);
			} else {
				/*
				 * No match was found; create a new instance, populate all
				 * fields using the copyService and add it to our result
				 * mergedSet.
				 */
				createNewInstanceAndMerge(mergedSet, incomingObj);
			}
		}

		/* Return the merged set */
		return mergedSet;
	}

	private void createNewInstanceAndMerge(Set mergedSet,
			AbstractEntity incomingObj) throws CopyServiceException {
		try {
			AbstractEntity onFileObj = (AbstractEntity) incomingObj.getClass()
					.newInstance();
			getCopyService().copy(incomingObj, onFileObj);
			mergedSet.add(onFileObj);
		} catch (Exception e) {
			throw new CopyServiceException("Unable to create new instance of "
					+ incomingObj.getClass().getName());
		}
	}

	private AbstractEntity getMatchingObjectInSet(AbstractEntity o, Set s) {
		MatchRuleService matchRuleService = getMatchRuleService();
		for (Iterator it = s.iterator(); it.hasNext();) {
			AbstractEntity setObj = (AbstractEntity) it.next();
			if (matchRuleService.match(o, setObj))
				return setObj;
		}
		return null;
	}

	/**
	 * @return Returns the matchRuleService.
	 */
	public MatchRuleService getMatchRuleService() {
		return matchRuleService;
	}

	/**
	 * @param matchRuleService
	 *            The matchRuleService to set.
	 */
	public void setMatchRuleService(MatchRuleService matchRuleService) {
		this.matchRuleService = matchRuleService;
	}

	/**
	 * @return Returns the matchRuleService.
	 */
	public CopyService getCopyService() {
		return copyService;
	}

	/**
	 * @param copyService
	 *            The copyService to set.
	 */
	public void setCopyService(CopyService copyService) {
		this.copyService = copyService;
	}

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

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

	private void mergeSSN(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		Set ssns = new HashSet(onFile.getSsns());
		Set mergedSsns = this.mergeSets(incoming.getSsns(), onFile.getSsns());

		// Remove all the ssns and add merged set
		for (Iterator iter = ssns.iterator(); iter.hasNext();) {
			onFile.removeSsn((SSN) iter.next());
		}

		for (Iterator iter = mergedSsns.iterator(); iter.hasNext();) {
			onFile.addSsn((SSN) iter.next());
		}
	}

	private void mergeAddress(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		Set addresses = incoming.getAddresses();

		if (addresses != null && addresses.size() > 0) {
			Set merged = this.mergeSets(addresses, onFile.getAddresses());
			onFile.removeAllAddresses();
			for (Iterator iter = merged.iterator(); iter.hasNext();) {
				Address address = (Address) iter.next();
				onFile.addAddress(address);
			}
		} else {
			onFile.removeAllAddresses();
		}
	}

	private void mergeAssociation(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		Set associations = incoming.getAssociations();
		if (associations != null && associations.size() > 0) {
			Set merged = this.mergeSets(associations, onFile.getAssociations());
			onFile.removeAllAssociations();
			for (Iterator iter = merged.iterator(); iter.hasNext();) {
				Association association = (Association) iter.next();
				Association incomingAssociation = null;
				if (association.getEntityKey() != null) {
					incomingAssociation = incoming
							.getAssociationByEntityKey(association
									.getEntityKey());
				}
				// To protect against case where Hibernate flushed and key was
				// out of sync
				if (incomingAssociation == null) {
					incomingAssociation = (Association) this
							.getMatchRuleService().findMatchingElement(
									association, incoming.getAssociations());
				}
				this.mergeAssociation(incomingAssociation, association); // merge
																			// address
				onFile.addAssociation(association);
			}
		} else {
			onFile.removeAllAssociations();
		}
	}

	private void mergeMilitaryService(Person incoming, Person onFile,
			boolean isUpdateFromGUI) throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		if (isUpdateFromGUI && incoming.getMilitaryService() != null) {
			MilitaryService militaryService = onFile.getMilitaryService();
			if (militaryService == null) {
				militaryService = new MilitaryService();
				onFile.setMilitaryService(militaryService);
			}
			this.getCopyService().copy(incoming.getMilitaryService(),
					militaryService);
			// Merge combat episodes
			Set combatEpisodes = incoming.getMilitaryService()
					.getCombatEpisodes();
			Set currentCombatEpisodes = onFile.getMilitaryService()
					.getCombatEpisodes();
			Set mergedCombatEpisodes = this.mergeSets(combatEpisodes,
					currentCombatEpisodes);
			onFile.getMilitaryService().removeAllCombatEpisodes();
			// Add merged combat episodes
			onFile.getMilitaryService().addAllCombatEpisodes(
					mergedCombatEpisodes);

			// Merge combat services
			// TODO not supported for this release only place holder

			// Merge site records
			Set siteRecords = incoming.getMilitaryService()
					.getMilitaryServiceSiteRecords();
			Set currentSiteRecords = onFile.getMilitaryService()
					.getMilitaryServiceSiteRecords();

			// Produce a merge set
			Set merged = this.mergeSets(siteRecords, currentSiteRecords);

			// Remove all records from on file to handle deletes
			onFile.getMilitaryService().removeAllMilitaryServiceSiteRecords();

			// Add back updated or new records
			for (Iterator iter = merged.iterator(); iter.hasNext();) {
				MilitaryServiceSiteRecord merge = (MilitaryServiceSiteRecord) iter
						.next();
				MilitaryServiceSiteRecord incomingSite = incoming
						.getMilitaryService()
						.getMilitaryServiceSiteRecordsBySite(merge.getSite());
				// Merge the episodes on each site record
				this.mergeMilitaryServiceSiteRecord(incomingSite, merge);
				// Replace the updated site record
				onFile.getMilitaryService().addMilitaryServiceSiteRecord(merge);
			}
		}
	}

	private void mergeDeathRecord(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		// Merge death record
		DeathRecord deathRecord = incoming.getDeathRecord();
		if (deathRecord == null) {
			onFile.setDeathRecord(null);
		} else {
			DeathRecord resultDeathRecord = onFile.getDeathRecord();
			if (resultDeathRecord == null) {
				resultDeathRecord = new DeathRecord();
				onFile.setDeathRecord(resultDeathRecord);
			}
			mergeDeathRecord(deathRecord, resultDeathRecord);
		}
	}

	private void mergeDemographic(Person incoming, Person onFile,
			boolean isUpdateFromGUI) throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		// Merge demographics
		mergeDemographic(incoming, onFile);

		if (isUpdateFromGUI) {
			// Some special demographic merge rules
			boolean compare = ObjectUtils.equals(incoming.getSensitiveRecord(),
					onFile.getSensitiveRecord());
			if (!compare) {
				// if the update is from GUI and Sensitivity Indicator has
				// Changed
				// Set source and time of update
				SensitivityChangeSource sensitivityChangeSource = this
						.getLookupService().getSensitivityChangeSourceByCode(
								SensitivityChangeSource.CODE_HEC.getName());
				onFile.setSensitivityChangeSource(sensitivityChangeSource);
				onFile.setSensitivityChangeDate(new Date());
				onFile.setSensitiveRecord(incoming.getSensitiveRecord());
				onFile.setSensitivityChangeSite(incoming
						.getSensitivityChangeSite());
			}
			// Defect 2060 required that we remove the claim folder attributes
			// from the copy service includes. Now we must copy these
			// separately,
			// but only when UI is the origin.
			onFile.setClaimFolderLocation(incoming.getClaimFolderLocation());
			onFile.setClaimFolderNumber(incoming.getClaimFolderNumber());
			// Added copy for user enrollee data
			onFile.setUserEnrolleeValidThrough(incoming
					.getUserEnrolleeValidThrough());
			onFile.setUserEnrolleeSite(incoming.getUserEnrolleeSite());
		} else {
			// Requirement 4159 and 4158 for message origin data
			// For an existing person if Enrollee Valid Through Date
			// is further in the future than the EDB Enrollee Valid Through
			// Date then accept the HL7 data and update in place.
			Integer existingYear = onFile.getUserEnrolleeValidThrough();
			Integer incomingYear = incoming.getUserEnrolleeValidThrough();
			if ((existingYear != null && incomingYear != null && incomingYear
					.compareTo(existingYear) > 0) || existingYear == null) {
				onFile.setUserEnrolleeValidThrough(incoming
						.getUserEnrolleeValidThrough());
				onFile.setUserEnrolleeSite(incoming.getUserEnrolleeSite());
			}
		}
		// Copy for data sync
		/* CCR10640 -- now supports multiple preferred facilites.  person.mostRecentPreferredFacility
		 * stores a calculated result.  Must keep the two in sync.
		 */
		this.mergePreferredFacilities(incoming, onFile);
		onFile.setMostRecentPreferredFacility(incoming.getMostRecentPreferredFacility());
	}

	private void mergeMedicaidFactor(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		if (incoming.getMedicaidFactor() != null) {
			MedicaidFactor medicaidFactor = onFile.getMedicaidFactor();
			if (medicaidFactor == null) {
				medicaidFactor = new MedicaidFactor();
				onFile.setMedicaidFactor(medicaidFactor);
			}
			this.mergeMedicaidFactor(incoming.getMedicaidFactor(),
					medicaidFactor);
		} else {
			onFile.setMedicaidFactor(null);
		}
	}

	private void mergePurpleHeart(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		if (incoming.getPurpleHeart() != null) {
			PurpleHeart ph = onFile.getPurpleHeart();
			if (ph == null) {
				ph = new PurpleHeart();
				onFile.addDecoration(ph);
			}
			this.mergePurpleHeart(incoming.getPurpleHeart(), ph);
		} else {
			onFile.removeAllDecorations();
		}
	}

	private void mergeCatastrophicDisability(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		CatastrophicDisability inCD = incoming.getCatastrophicDisability();
		CatastrophicDisability onFileCD = onFile.getCatastrophicDisability();
		if (inCD != null) {
			if (onFileCD == null) {
				onFileCD = new CatastrophicDisability();
				onFile.addClinicalDetermination(onFileCD);
			}
			this.mergeCatastrophicDisability(inCD, onFileCD);
		} else {
			onFile.removeClinicalDeterminationByType(CatastrophicDisability.class);
		}
	}

	private void mergeFeeBasis(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		Set merged = this.mergeSets(incoming.getFeeBasis(),
				onFile.getFeeBasis());
		onFile.removeAllFeeBasis();

		for (Iterator iter = merged.iterator(); iter.hasNext();) {
			FeeBasis feeBasis = (FeeBasis) iter.next();
			onFile.addFeeBasis(feeBasis);
		}
	}

	public void mergeReceivedEligibility(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		ReceivedEligibility inPrimary = incoming
				.getReceivedPrimaryEligibility();
		ReceivedEligibility currPrimary = onFile
				.getReceivedPrimaryEligibility();
		if (inPrimary != null && isValidReceivedCode(inPrimary)) {
			if (currPrimary == null) {
				currPrimary = new ReceivedEligibility();
				onFile.setReceivedPrimaryEligiblity(currPrimary);
			}
			this.mergeReceivedEligibility(inPrimary, currPrimary);
		} else {
			onFile.setReceivedPrimaryEligiblity(null);
		}
		Set validReceivedSecondaries = getValidReceivedEligibilities(incoming
				.getReceivedSecondaryEligibilities());
		Set merged = this.mergeSets(validReceivedSecondaries,
				onFile.getReceivedSecondaryEligibilities());
		onFile.removeAllReceivedSecondaryEligibilities();
		for (Iterator iter = merged.iterator(); iter.hasNext();) {
			ReceivedEligibility merge = (ReceivedEligibility) iter.next();
			onFile.addReceivedSecondaryEligibility(merge);
		}
	}

	private Set getValidReceivedEligibilities(Set received) {
		HashSet valids = new HashSet();
		if (received == null || received.size() == 0)
			return valids;
		Iterator iter = received.iterator();
		while (iter.hasNext()) {
			ReceivedEligibility receivedEligibility = (ReceivedEligibility) iter
					.next();
			if (isValidReceivedCode(receivedEligibility)) {
				valids.add(receivedEligibility);
			}
		}
		return valids;
	}

	private boolean isValidReceivedCode(ReceivedEligibility re) {
		if (re != null) {
			if (re.getType().getCode()
					.equals(EligibilityType.ALLIED_VETERAN.getName())) {
				if (re.getAlliedCountry() == null) {
					return false;
				}
			} else if (re.getType().getCode()
					.equals(EligibilityType.OTHER_FEDERAL_AGENCY.getName())) {
				if (re.getOtherFederalAgency() == null) {
					return false;
				}
			}
		}
		return true;
	}

	public void mergePhone(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		Set merged = this.mergeSets(incoming.getPhones(), onFile.getPhones());
		onFile.removeAllPhones();
		for (Iterator iter = merged.iterator(); iter.hasNext();) {
			Phone merge = (Phone) iter.next();
			onFile.addPhone(merge);
		}
	}

	public void mergeEmail(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		Set merged = this.mergeSets(incoming.getEmails(), onFile.getEmails());
		onFile.removeAllEmails();
		for (Iterator iter = merged.iterator(); iter.hasNext();) {
			Email merge = (Email) iter.next();
			onFile.addEmail(merge);
		}
	}

	private void mergeInsurancePolicy(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		Set insurances = incoming.getInsurances();

		if (insurances != null && !insurances.isEmpty()) {
			Set updated = new HashSet();
			for (Iterator i = insurances.iterator(); i.hasNext();) {
				InsurancePolicy policy = (InsurancePolicy) i.next();
				InsurancePolicy update = this.mergeInsurancePolicy(policy,
						onFile);
				updated.add(update);
			}
			onFile.removeAllInsurances();
			onFile.addAllInsurances(updated);
		} else {
			onFile.removeAllInsurances();
		}
	}

	private void mergeSignatureImage(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		Set merged = this.mergeSets(incoming.getSignatureImages(),
				onFile.getSignatureImages());
		onFile.removeAllSignatureImages();
		for (Iterator iter = merged.iterator(); iter.hasNext();) {
			SignatureImage merge = (SignatureImage) iter.next();
			onFile.addSignatureImage(merge);
		}
	}

	private void mergeEnrollmentOverride(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		EnrollmentOverride incomingOverride = incoming.getEnrollmentOverride();
		if (incomingOverride != null) {
			EnrollmentOverride curr = onFile.getEnrollmentOverride();
			if (curr == null) {
				curr = new EnrollmentOverride();
				onFile.setEnrollmentOverride(curr);
			}
			this.mergeEnrollmentOverride(incomingOverride, curr);
		} else {
			onFile.setEnrollmentOverride(null);
		}
	}

	private void mergeMilitarySexualTrauma(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		MilitarySexualTrauma inMST = incoming.getMilitarySexualTrauma();
		MilitarySexualTrauma onFileMST = onFile.getMilitarySexualTrauma();
		if (inMST != null) {
			if (onFileMST == null) {
				onFileMST = new MilitarySexualTrauma();
				onFile.addClinicalDetermination(onFileMST);
			}
			this.mergeMilitarySexualTrauma(inMST, onFileMST);
		} else {
			onFile.removeClinicalDeterminationByType(MilitarySexualTrauma.class);
		}
	}

	private void mergeNoseThroatRadium(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		NoseThroatRadium inNTR = incoming.getNoseThroatRadium();
		NoseThroatRadium onFileNTR = onFile.getNoseThroatRadium();
		if (inNTR != null) {
			if (onFileNTR == null) {
				onFileNTR = new NoseThroatRadium();
				onFile.addClinicalDetermination(onFileNTR);
			}
			this.mergeNoseThroatRadium(inNTR, onFileNTR);
		} else {
			onFile.removeClinicalDeterminationByType(NoseThroatRadium.class);
		}
	}

	private void mergePatientVisitSummary(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		Map incomingSummaries = incoming.getPatientVisitSummaries();
		Map onFileSummaries = onFile.getPatientVisitSummaries();
		Map toRemove = new HashMap(onFileSummaries);
		if (incomingSummaries.size() > 0) {
			Map merged = mergeMaps(incomingSummaries, onFileSummaries);

			// remove from onfile
			for (Iterator iter = toRemove.keySet().iterator(); iter.hasNext();) {
				SiteYear key = (SiteYear) iter.next();
				onFile.removePatientVisitSummary(key.getFacility());
			}
			// add back to onfile
			for (Iterator iter = merged.keySet().iterator(); iter.hasNext();) {
				SiteYear key = (SiteYear) iter.next();
				PatientVisitSummary summary = (PatientVisitSummary) merged
						.get(key);
				onFile.setPatientVisitSummary(key.getYear(), key.getFacility(),
						summary);
			}
		} else {
			for (Iterator iter = toRemove.keySet().iterator(); iter.hasNext();) {
				SiteYear key = (SiteYear) iter.next();
				onFile.removePatientVisitSummary(key.getFacility());
			}
		}
	}

	public void mergeFinancialData(Person incoming, Person onFile)
			throws ServiceException {
		Validate.notNull(onFile, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		Map statements = incoming.getFinancialStatements();
		if (statements.size() > 0) {
			Map onFileStatements = onFile.getFinancialStatements();
			// Merge parent objects
			Map merged = mergeMaps(statements, onFileStatements);
			// Merge dependent objects
			for (Iterator iter = merged.keySet().iterator(); iter.hasNext();) {
				Integer key = (Integer) iter.next();
				FinancialStatement incomingStatement = incoming
						.getFinancialStatement(key);

				FinancialStatement mergedStatement = (FinancialStatement) merged
						.get(key);
				if (incomingStatement != null) {
					this.mergeFinancialStatement(incomingStatement, mergedStatement);
				}
			}
			onFile.removeAllFinancialStatements();
			// Add back to on file person
			for (Iterator iter = merged.keySet().iterator(); iter.hasNext();) {
				Integer key = (Integer) iter.next();
				onFile.setFinancialStatement(key,
						(FinancialStatement) merged.get(key));
			}
		} else {
			onFile.removeAllFinancialStatements();
		}

		Map tests = incoming.getIncomeTests();
		if (tests.size() > 0) {
			Map onfileTests = onFile.getIncomeTests();
			// Merge parent objects
			Map merged = mergeMaps(tests, onfileTests);
			// Merge dependent objects
			for (Iterator iter = merged.keySet().iterator(); iter.hasNext();) {
				Integer key = (Integer) iter.next();
				IncomeTest incomingTest = incoming.getIncomeTest(key);
				IncomeTest mergedTest = (IncomeTest) merged.get(key);
				if (incomingTest != null) {
					mergeFullIncomeTest(incomingTest, mergedTest);
				}
			}
			onFile.removeAllIncomeTests();
			// Add back to on file person
			for (Iterator iter = merged.keySet().iterator(); iter.hasNext();) {
				Integer key = (Integer) iter.next();
				onFile.setIncomeTest(key, (IncomeTest) merged.get(key));
			}
		} else {
			onFile.removeAllIncomeTests();
		}

	}

	/**
	 * This method merges ReportSetup.
	 *
	 * @param incoming
	 * @param onFile
	 * @throws ServiceException
	 */
	public void mergeReportSetup(ReportSetup incoming, ReportSetup onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An incoming ReportSetup must not be null ");
		Validate.notNull(onFile, "An onFile ReportSetup must not be null ");
		copyService.copy(incoming, onFile);

		ReportParameterSet inParam = incoming.getParameterSet();
		ReportParameterSet onParam = onFile.getParameterSet();
		if (inParam != null) {
			if (onParam == null) {
				onParam = new ReportParameterSet();
			}
			onFile.setParameterSet(onParam);
			this.mergeReportParameterSet(inParam, onParam);
		} else {
			onFile.setParameterSet(null);
		}
		onFile.setParameterSet(onParam);
		ReportSchedule inSchedule = incoming.getSchedule();
		ReportSchedule onSchedule = onFile.getSchedule();
		if (inSchedule != null) {
			if (onSchedule == null) {
				onSchedule = new ReportSchedule();
			}
			onFile.setSchedule(onSchedule);
			this.mergeReportSchedule(inSchedule, onSchedule);
		} else {
			onFile.setSchedule(null);
		}

	}

	/**
	 * This method merges ReportParameterSet
	 *
	 * @param incoming
	 * @param onFile
	 * @throws ServiceException
	 */
	public void mergeReportParameterSet(ReportParameterSet incoming,
			ReportParameterSet onFile) throws ServiceException {
		Validate.notNull(incoming,
				"An incoming ReportParameterSet must not be null ");
		Validate.notNull(onFile,
				"An onFile ReportParameterSet must not be null ");
		copyService.copy(incoming, onFile);

		Set mergedUsers = this
				.mergeSets(incoming.getUsers(), onFile.getUsers());
		onFile.removeAllUsers();
		onFile.addAllUsers(mergedUsers);

		Set mergedMsgTypes = this.mergeSets(incoming.getMessageTypes(),
				onFile.getMessageTypes());
		onFile.removeAllMessageTypes();
		onFile.addAllMessageTypes(mergedMsgTypes);

		Set mergedBadAddrResons = this.mergeSets(
				incoming.getBadAddressReasons(), onFile.getBadAddressReasons());
		onFile.removeAllBadAddressReasons();
		onFile.addAllBadAddressReasons(mergedBadAddrResons);

		Set mergedCancelDeclineReasons = this.mergeSets(
				incoming.getCancelDeclineReasons(),
				onFile.getCancelDeclineReasons());
		onFile.removeAllCancelDeclineReasons();
		onFile.addAllCancelDeclineReasons(mergedCancelDeclineReasons);

		Set mergedPHRejectionRemarks = this.mergeSets(
				incoming.getPHRejectionRemarks(),
				onFile.getPHRejectionRemarks());
		onFile.removeAllPHRejectionRemarks();
		onFile.addAllPHRejectionRemarks(mergedPHRejectionRemarks);

		Set mergedReportEnrollmentPriorityGroups = this.mergeSets(
				incoming.getReportEnrollmentPriorityGroups(),
				onFile.getReportEnrollmentPriorityGroups());
		onFile.removeAllReportEnrollmentPriorityGroups();
		onFile.addAllReportEnrollmentPriorityGroups(mergedReportEnrollmentPriorityGroups);

		Set mergedEnrollmentCategories = this.mergeSets(
				incoming.getEnrollmentCategories(),
				onFile.getEnrollmentCategories());
		onFile.removeAllEnrollmentCategories();
		onFile.addAllEnrollmentCategories(mergedEnrollmentCategories);

		Set mergedEnrollmentStatusComponents = this.mergeSets(
				incoming.getEnrollmentStatusComponents(),
				onFile.getEnrollmentStatusComponents());
		onFile.removeAllEnrollmentStatusComponents();
		onFile.addAllEnrollmentStatusComponents(mergedEnrollmentStatusComponents);

		Set mergedPersonMergeStatuses = this.mergeSets(
				incoming.getPersonMergeStatuses(),
				onFile.getPersonMergeStatuses());
		onFile.removeAllPersonMergeStatuses();
		onFile.addAllPersonMergeStatuses(mergedPersonMergeStatuses);

		Set mergedEligibilityFactors = this.mergeSets(
				incoming.getEligibilityFactors(),
				onFile.getEligibilityFactors());
		onFile.removeAllEligibilityFactors();
		onFile.addAllEligibilityFactors(mergedEligibilityFactors);

		Set mergedEnrollmentStatuses = this.mergeSets(
				incoming.getEnrollmentStatuses(),
				onFile.getEnrollmentStatuses());
		onFile.removeAllEnrollmentStatuses();
		onFile.addAllEnrollmentStatuses(mergedEnrollmentStatuses);

		Set mergedEnrollmentOverrideReasons = this.mergeSets(
				incoming.getEnrollmentOverrideReasons(),
				onFile.getEnrollmentOverrideReasons());
		onFile.removeAllEnrollmentOverrideReasons();
		onFile.addAllEnrollmentOverrideReasons(mergedEnrollmentOverrideReasons);

		Set mergedQueryToSites = this.mergeSets(incoming.getQueryToSites(),
				onFile.getQueryToSites());
		onFile.removeAllQueryToSites();
		onFile.addAllQueryToSites(mergedQueryToSites);

		Set mergedVBAQueryStatuses = this.mergeSets(
				incoming.getVBAQueryStatuses(), onFile.getVBAQueryStatuses());
		onFile.removeAllVBAQueryStatuses();
		onFile.addAllVBAQueryStatuses(mergedVBAQueryStatuses);

		Set mergedReportEEDWeeklyReportTypes = this.mergeSets(
				incoming.getReportEEDWeeklyReportTypes(),
				onFile.getReportEEDWeeklyReportTypes());
		onFile.removeAllReportEEDWeeklyReportTypes();
		onFile.addAllReportEEDWeeklyReportTypes(mergedReportEEDWeeklyReportTypes);

		Set mergedFacilities = this.mergeSets(incoming.getFacilities(),
				onFile.getFacilities());
		onFile.removeAllFacilities();
		onFile.addAllFacilities(mergedFacilities);

		Set mergedFunctionalGroups = this.mergeSets(
				incoming.getFunctionalGroups(), onFile.getFunctionalGroups());
		onFile.removeAllFunctionalGroups();
		onFile.addAllFunctionalGroups(mergedFunctionalGroups);

		Set mergedWkfCaseStatusTypes = this.mergeSets(
				incoming.getWkfCaseStatusTypes(),
				onFile.getWkfCaseStatusTypes());
		onFile.removeAllWkfCaseStatusTypes();
		onFile.addAllWkfCaseStatusTypes(mergedWkfCaseStatusTypes);

		Set mergedWkfCaseTypes = this.mergeSets(incoming.getWkfCaseTypes(),
				onFile.getWkfCaseTypes());
		onFile.removeAllWkfCaseTypes();
		onFile.addAllWkfCaseTypes(mergedWkfCaseTypes);

		Set mergedMessageErrors = this.mergeSets(incoming.getMessageErrors(),
				onFile.getMessageErrors());
		onFile.removeAllMessageErrors();
		onFile.addAllMessageErrors(mergedMessageErrors);

		Set mergedRegistryTypes = this.mergeSets(incoming.getRegistryTypes(),
				onFile.getRegistryTypes());
		onFile.removeAllRegistryTypes();
		onFile.addAllRegistryTypes(mergedRegistryTypes);

		Set mergedLetterFiles = this.mergeSets(
				incoming.getReportLinkLetterFiles(),
				onFile.getReportLinkLetterFiles());
		onFile.removeAllReportLinkLetterFiles();
		onFile.addAllReportLinkLetterFiles(mergedLetterFiles);

		Set mergedLetterRejectreasons = this.mergeSets(
				incoming.getLetterRejectReasons(),
				onFile.getLetterRejectReasons());
		onFile.removeAllLetterRejectReasons();
		onFile.addAllLetterRejectReasons(mergedLetterRejectreasons);

		Set mergedLetterErrorTypes = this.mergeSets(
				incoming.getLetterErrorTypes(), onFile.getLetterErrorTypes());
		onFile.removeAllLetterErrorTypes();
		onFile.addAllLetterErrorTypes(mergedLetterErrorTypes);
	}

	/**
	 * This method merges ReportSchedule.
	 *
	 * @param incoming
	 * @param onFile
	 * @throws ServiceException
	 */
	public void mergeReportSchedule(ReportSchedule incoming,
			ReportSchedule onFile) throws ServiceException {
		Validate.notNull(incoming,
				"An incoming ReportSchedule must not be null ");
		Validate.notNull(onFile, "An onFile ReportSchedule must not be null ");
		copyService.copy(incoming, onFile);

		Set mergedDaysOfWeek = this.mergeSets(incoming.getDaysOfWeek(),
				onFile.getDaysOfWeek());
		onFile.removeAllDaysOfWeek();
		onFile.addAllDaysOfWeek(mergedDaysOfWeek);
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeIdentityTraits(gov.va.med.esr.common.model.person.Person,
	 *      gov.va.med.esr.common.model.person.Person)
	 */
	public Person mergeIdentityTraits(Person incoming, Person onFile)
			throws ServiceException {
		return this.mergeIdentityTraits(incoming, onFile, false);
	}

	/**
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeIdentityTraits(gov.va.med.esr.common.model.person.Person,
	 *      gov.va.med.esr.common.model.person.Person)
	 */
	public Person mergeIdentityTraits(Person incoming, Person onFile,
			boolean isPersonMerge) throws ServiceException {
		Validate.notNull(incoming, "An incoming person must not be null ");
		Validate.notNull(onFile, "An onFile person must not be null ");

		if (!isPersonMerge) {
			this.mergeSSN(incoming, onFile);
		}

		Name incomingLegalName = incoming.getLegalName();

		if (incomingLegalName != null) {
			Name onFileLegalName = onFile.getLegalName();
			if (onFileLegalName == null) {
				onFileLegalName = new Name();
				onFile.setLegalName(onFileLegalName);
			}
			this.getCopyService().copy(incomingLegalName, onFileLegalName);
		} else {
			onFile.setLegalName(null);
		}

		onFile.setGender(incoming.getGender());
		onFile.setMothersMaidenName(incoming.getMothersMaidenName());

		BirthRecord incomingBirthRecord = incoming.getBirthRecord();

		if (incomingBirthRecord != null) {
			BirthRecord onFileBirthRecord = onFile.getBirthRecord();
			if (onFileBirthRecord == null) {
				onFileBirthRecord = new BirthRecord();
				onFile.setBirthRecord(onFileBirthRecord);
			}
			this.getCopyService().copy(incomingBirthRecord, onFileBirthRecord);
		} else {
			onFile.setBirthRecord(null);
		}

		return onFile;
	}

	public void mergeUserAccount(ESRUserPrincipalImpl incoming,
			ESRUserPrincipalImpl onFile) throws ServiceException {
		Validate.notNull(incoming, "An incoming user must not be null ");
		Validate.notNull(onFile, "An onFile user must not be null ");
		getCopyService().copy(incoming, onFile);
	}

	public void mergeUserProfile(ESRUserPrincipalImpl incoming,
			ESRUserPrincipalImpl onFile) throws ServiceException {
		Validate.notNull(incoming, "An incoming user must not be null ");
		Validate.notNull(onFile, "An onFile user must not be null ");
		// merge roles
		// merge capability sets
		// merge capabilities
	}

	public Employment mergeEmployment(Employment incoming, Employment onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An incoming  employment not be null");
		Validate.notNull(onFile,
				"An incoming on file employment must not be null");
		this.getCopyService().copy(incoming, onFile);
		mergeEmploymentAddress(incoming.getEmployerAddress(), onFile);
		return onFile;
	}

	public Employment mergeEmploymentAddress(Address incoming, Employment onFile)
			throws ServiceException {
		if (incoming == null) {
			onFile.setEmployerAddress(null);
		} else {
			Address currentAddress = onFile.getEmployerAddress();

			if (currentAddress == null) {
				currentAddress = new Address();
				mergeAddress(incoming, currentAddress);
				onFile.setEmployerAddress(currentAddress);
			} else {
				mergeAddress(incoming, currentAddress);
			}
		}

		return onFile;
	}

	/**
	 * Merge incoming Medal Of Honor with result.
	 *
	 * @param incoming
	 * @param result
	 * @return MedalOfHonor
	 * @throws ServiceException
	 */
	public MedalOfHonor mergeMedalOfHonor(MedalOfHonor incoming,
			MedalOfHonor result) throws ServiceException {
		Validate.notNull(incoming, "A incoming Medal Of Honor must not be null");
		Validate.notNull(result, "A Medal Of Honor on file must not be null");

		// Use copy to copy lookups and status data
		getCopyService().copy(incoming, result);

		return result;
	}

	public void mergeSeedingMilitaryServiceEpisode(
			MilitaryServiceEpisode incomingEpisode,
			MilitaryServiceEpisode overlappedEpisode,
			MilitaryServiceSiteRecord onFile) {
		Validate.notNull(incomingEpisode,
				"A MilitaryServiceEpisode must not be null");
		Validate.notNull(onFile,
				"A MilitaryServiceSiteRecord on file must not be null");

		try {
			if (overlappedEpisode == null) {

				MilitaryServiceEpisode mse = new MilitaryServiceEpisode();
				this.mergeMilitaryServiceEpisode(incomingEpisode, mse);
				onFile.addMilitaryServiceEpisode(mse);
			} else {
				this.mergeMilitaryServiceEpisode(incomingEpisode,
						overlappedEpisode);
			}

		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge an MilitaryServiceEpisode", ex);
			throw new RuntimeException(
					"Failed to merge an MilitaryServiceEpisode", ex);
		}

	}

	public Set mergeRatedDisabilities(Set incomingDisabilities,
			Set onFileDisabilities) throws ServiceException {
		return mergeSets(incomingDisabilities, onFileDisabilities, true);
	}

	/**
	 * CCR 11415 - null dates from VBA overwriting non-null dates
	 *
	 * Method to apply HL7 specific rules for merging a rated disability from site such as VBA
	 *
	 * @see gov.va.med.esr.common.rule.service.MergeRuleService#mergeRatedDisabilitiesFromHL7(java.util.Set, java.util.Set)
	 */
	public Set mergeRatedDisabilitiesFromHL7(Set incomingDisabilities, Set onFileDisabilities) throws ServiceException {
		Validate.notNull(incomingDisabilities, "incomingDisabilities must not be null");
		Validate.notNull(onFileDisabilities, "onFileDisabilities must not be null");
		Set onFileRDs = new HashSet(onFileDisabilities);
		Set incoming = new HashSet(incomingDisabilities);
		Set done = new HashSet();
		Set addRds = new HashSet();
		try {
			// Find exact matches first
			for (Iterator iterRDs = incoming.iterator(); iterRDs.hasNext();) {
				RatedDisability incomingRD = (RatedDisability) iterRDs.next();
				RatedDisability found = null;

				found = findExactMatchingRatedDisabilityFromHL7(incomingRD, onFileRDs);
				if (found != null) {
					onFileRDs.remove(found);
					ImpreciseDate holdOrigEffDt = found.getOriginalEffectiveRatingDate();
					ImpreciseDate holdRatingDate = found.getRatingDate();

					this.mergeRatedDisability(incomingRD, found);
					if (holdOrigEffDt != null && found.getOriginalEffectiveRatingDate() == null) {
						found.setOriginalEffectiveRatingDate(holdOrigEffDt);
					}
					if (holdRatingDate != null && found.getRatingDate()== null) {
						found.setRatingDate(holdRatingDate);
					}
					addRds.add(found);
					done.add(incomingRD);
				}
			}
			incoming.removeAll(done);

			for (Iterator iterRDs = incoming.iterator(); iterRDs.hasNext();) {
				RatedDisability incomingRD = (RatedDisability) iterRDs.next();
				RatedDisability found = null;

				found = (RatedDisability)this.getMatchRuleService().findMatchingElement(incomingRD, onFileRDs);

				if (found != null) {
					onFileRDs.remove(found);
					ImpreciseDate holdOrigEffDt = found.getOriginalEffectiveRatingDate();
					ImpreciseDate holdRatingDate = found.getRatingDate();

					this.mergeRatedDisability(incomingRD, found);
					if (holdOrigEffDt != null && found.getOriginalEffectiveRatingDate() == null) {
						found.setOriginalEffectiveRatingDate(holdOrigEffDt);
					}
					if (holdRatingDate != null && found.getRatingDate()== null) {
						found.setRatingDate(holdRatingDate);
					}
					addRds.add(found);
				}

				if (found == null) {
					RatedDisability newRd = new RatedDisability();
					this.mergeRatedDisability(incomingRD, newRd);
					addRds.add(newRd);
				}
			}
		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge a ServiceConnectionAward", ex);
			throw new RuntimeException(
					"Failed to merge a ServiceConnectionAward", ex);
		}
		return addRds;
	}

	private RatedDisability findExactMatchingRatedDisabilityFromHL7(RatedDisability incoming, Set disabilities) {
		Validate.notNull(incoming, "incoming rated disability must not be null");
		Validate.notNull(disabilities, "incoming disabilities must not be null");

		for (Iterator iter = disabilities.iterator(); iter.hasNext();) {
			RatedDisability rd = (RatedDisability) iter.next();
			if (ObjectUtils.equals(incoming.getDisability(), rd.getDisability()) &&
					ObjectUtils.equals(incoming.getDiagnosticExtremity(), rd.getDiagnosticExtremity()) &&
					ObjectUtils.equals(incoming.getOriginalEffectiveRatingDate(), rd.getOriginalEffectiveRatingDate()) &&
					ObjectUtils.equals(incoming.getRatingDate(), rd.getRatingDate()) &&
					ObjectUtils.equals(incoming.isServiceConnected(), rd.isServiceConnected()) &&
					ObjectUtils.equals(incoming.getPercentage(), rd.getPercentage())) {
				return rd;
			}
		}

		return null;

	}
	public void mergeIncomingMSEToHEC(MilitaryServiceSiteRecord incoming,
			MilitaryServiceSiteRecord onFile) {

		Validate.notNull(onFile,
				"A MilitaryServiceSiteRecord on file must not be null");

		try {
			//Copy site record fields except the MSEs and CEs.
			this.getCopyService().copy(incoming, onFile);
			for (Iterator iter = incoming.getMilitaryServiceEpisodes()
					.iterator(); iter.hasNext();) {
				MilitaryServiceEpisode incomingEpisode = (MilitaryServiceEpisode) iter
				.next();

				// Newly added MilitaryServiceEpisode, just add it
				MilitaryServiceEpisode mse = new MilitaryServiceEpisode();
				this.mergeMilitaryServiceEpisode(incomingEpisode, mse);
				onFile.addMilitaryServiceEpisode(mse);

			}


		} catch (ServiceException ex) {
			this.logger.debug("Failed to merge an MilitaryServiceEpisode", ex);
			throw new RuntimeException(
					"Failed to merge an MilitaryServiceEpisode", ex);
		}

	}

	public void mergeHealthCareProvider(HealthCareProvider incoming, HealthCareProvider onFile) throws ServiceException {
		Validate.notNull(incoming,	"An incoming HealthCareProvider must not be null");
		Validate.notNull(onFile,	"An incoming on file HealthCareProvider must not be null");

		Name incomingName = incoming.getName();
		Name onFileName = onFile.getName();

		if (incomingName == null) {
			// Delete the existing data
			onFile.setName(null);
		} else if (onFileName != null) {
			//	If found, update the data
			this.getCopyService().copy(incomingName, onFileName);
		} else {
			// Create a new instance to insert to the record on file
			Name newName = new Name();
			this.getCopyService().copy(incomingName, newName);
			onFileName = new Name();
			onFile.setName(newName);
		}
		this.getCopyService().copy(incoming, onFile);
	}

	public void mergeProviderAssignment(PatientProviderAssignmentLite incoming, PatientProviderAssignmentLite onFile) throws ServiceException {
		Validate.notNull(incoming,	"An incoming PatientProviderAssignmentLite must not be null");
		Validate.notNull(onFile,	"An incoming on file PatientProviderAssignmentLite must not be null");

		this.getCopyService().copy(incoming, onFile);
		onFile.setProvider(incoming.getProvider());

	}


	private void mergePreferredFacilities(Person incoming, Person onFile)
			throws ServiceException {

			Validate.notNull(incoming, "An incoming person must not be null ");
			Validate.notNull(onFile, "An onFile person must not be null ");

			Set pfs = incoming.getPreferredFacilities();

			if (pfs != null && pfs.size() > 0) {
				Set merged = this.mergeSets(pfs, onFile.getPreferredFacilities());
				onFile.removeAllPreferredFacilities();
				for (Iterator iter = merged.iterator(); iter.hasNext();) {
					PreferredFacility pf = (PreferredFacility) iter.next();
					onFile.addPreferredFacility(pf);
				}
			} else {
				//CCR 12722 - only perform if the onFile's set is non-empty
				if (onFile.getPreferredFacilities() != null && onFile.getPreferredFacilities().size() > 0) {
					onFile.removeAllPreferredFacilities();
				}
			}

			/* CCR10640 -- now supports multiple preferred facilites.  person.mostRecentPreferredFacility
			 * stores a calculated result.  Must keep the two in sync.
			 */
			onFile.setMostRecentPreferredFacility(incoming.getMostRecentPreferredFacility());
		}

	////////////////////////////////////////CLV////////////////////////////////////////////////////
	public void mergeCampLejeuneVerification(CampLejeuneVerification incoming, CampLejeuneVerification onFile) throws ServiceException {
		Validate.notNull(incoming, "An incoming CampLejeuneVerification must not be null ");
		Validate.notNull(onFile, "An onFile CampLejeuneVerification must not be null ");
		// Merge other part:

		//Don't do this! The result person indicator already determined by ilog rules, don't change it here!!!
		if (incoming.getSpecialFactorIndicator() != null) {
			onFile.setSpecialFactorIndicator(incoming.getSpecialFactorIndicator());
		} else {
			onFile.setSpecialFactorIndicator(null);
		}
		onFile.setChangeDate(incoming.getChangeDate());
		onFile.setChangeSite(incoming.getChangeSite());
		onFile.setChangeSource(incoming.getChangeSource());
		onFile.setRegistrationDate(incoming.getRegistrationDate());

		Set<CampLejeuneVerificationMethod> incomingCLVM = incoming.getCampLejeuneVerificationMethods();

		if (incomingCLVM != null && incomingCLVM.size() > 0) {

			// Update ES with incoming verified method and comments (if
			// different and incoming Verified Method is not blank)
			if (!StringUtils.equals(incoming.getComments(), onFile.getComments())) {
				onFile.setComments(incoming.getComments());
			}
			// remove any deletes
			Set<CampLejeuneVerificationType> removeList = new HashSet<CampLejeuneVerificationType>();

			for (CampLejeuneVerificationMethod ofcm : onFile.getCampLejeuneVerificationMethods()) {
				if (incoming.getCampLejeuneMethodByType(ofcm.getVerificationType().getCode()) == null) {
					removeList.add(ofcm.getVerificationType());
				}
			}
			// now really remove
			for (CampLejeuneVerificationType tobeRemoved : removeList) {
				onFile.removeCampLejeuneVerificationMethodByType(tobeRemoved);
			}

			// add new ones
			Set<CampLejeuneVerificationType> addList = new HashSet<CampLejeuneVerificationType>();
			for (CampLejeuneVerificationMethod clvm : incomingCLVM) {
				if (onFile.getCampLejeuneMethodByType(clvm.getVerificationType().getCode()) == null) {
					addList.add(clvm.getVerificationType());
				}
			}
			// now really add
			for (CampLejeuneVerificationType tobeAdded : addList) {
				CampLejeuneVerificationMethod newCLVM = new CampLejeuneVerificationMethod();
				newCLVM.setVerificationType(tobeAdded);
				onFile.addCampLejeuneVerificationMethod(newCLVM);

			}

		} else {
			//Update ES with incoming verified method and comments (if different and incoming Verified Method is not blank)
			//since incoming methods are empty here, don't wipe out onFile method here according to the requirement above
/*			if (onFile.getCampLejeuneVerificationMethods() != null && onFile.getCampLejeuneVerificationMethods().size() > 0) {
				onFile.removeAllCampLejeuneVerificationMethods();
			}*/
		}
	}

 public void mergeCLVerificationMethod(CampLejeuneVerificationMethod incoming, CampLejeuneVerificationMethod onFile) throws ServiceException
 {
	 Validate.notNull(incoming, "An incoming CampLejeuneVerificationMethod must not be null");
		Validate.notNull(onFile, "An on file CampLejeuneVerificationMethod must not be null");
		this.getCopyService().copy(incoming, onFile);

 }
//////////////////////////////////////CLV/////////////////////////////////////////////////////




	//CCR 13028 - refactored to prevent a history insert upon every update
	public void mergeHealthBenefitProfile(HealthBenefitProfile incoming, HealthBenefitProfile onFile)
			throws ServiceException {

			Validate.notNull(incoming, "An incoming HealthBenefitProfile must not be null ");
			Validate.notNull(onFile, "An onFile HealthBenefitProfile must not be null ");

			Set incominghealthBenefitPlans = incoming.getHealthBenefitPlans();
			Set onFileHealthBenefitPlans = onFile.getHealthBenefitPlans();

			if (incominghealthBenefitPlans != null && incominghealthBenefitPlans.size() > 0) {
				//find match by hbp type and copy in place
				//if no match then add it

				for (Iterator iter = incominghealthBenefitPlans.iterator(); iter.hasNext();) {
					HealthBenefitPlan incomingHBP = (HealthBenefitPlan) iter.next();
					HealthBenefitPlan newHbp = new HealthBenefitPlan();
					boolean matched = false;
					HealthBenefitPlan onFileHBP = null;
					for (Iterator i = onFileHealthBenefitPlans.iterator(); i.hasNext();) {
						onFileHBP = (HealthBenefitPlan) i.next();
						if (incomingHBP.getPlanType().getCode().equals(onFileHBP.getPlanType().getCode())) {
							matched = true;
							mergeHealthBenefitPlan(incomingHBP, onFileHBP);
							break;
						}
					}
					//no match so it's a new addition
					if (!matched) {
						this.getCopyService().copy(incomingHBP, newHbp);
						onFile.addHealthBenefitPlan(newHbp);
					}
				}

				//remove any deletes
				Set onFileHBPs = new HashSet(onFile.getHealthBenefitPlans());
				for (Iterator iter = onFileHBPs.iterator(); iter.hasNext();) {
					HealthBenefitPlan hbp = (HealthBenefitPlan) iter.next();

					if (incoming.getHealthBenefitPlanByType(hbp.getPlanType()) == null) {
						onFile.removeHealthBenefitPlan(hbp);
					}
				}


			// CCR 12722 Removed following line, redundant.
			// this.getCopyService().copy(incoming, onFile);

		}
	}

	public HealthBenefitPlan mergeHealthBenefitPlan(HealthBenefitPlan incoming, HealthBenefitPlan onFile)
			throws ServiceException {
		Validate.notNull(incoming, "An incoming plan must not be null");
		Validate.notNull(onFile, "An on file plan must not be null");
		this.getCopyService().copy(incoming, onFile);
		return onFile;
	}


	private AbstractEntity matchDomainConceptInSet(AbstractEntity o, Set s) {
		for (Iterator it = s.iterator(); it.hasNext();) {
			AbstractEntity setObj = (AbstractEntity) it.next();
			boolean matches = AbstractEntity.matchesDomainConcept(o, setObj);
			if (matches) {
				return setObj;
			}
		}
		return null;
	}
}