org.openhie.openempi.nhinadapter.hl7.Hl7ConversionHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.openhie.openempi.nhinadapter.hl7.Hl7ConversionHelper.java

Source

/**
 *
 * Copyright (C) 2002-2012 "SYSNET International, Inc."
 * support@sysnetint.com [http://www.sysnetint.com]
 *
 * This file is part of OpenEMPI.
 *
 * OpenEMPI is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.openhie.openempi.nhinadapter.hl7;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.xml.bind.JAXBElement;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hl7.v3.ADExplicit;
import org.hl7.v3.AcknowledgementType;
import org.hl7.v3.ActClassControlAct;
import org.hl7.v3.AdxpExplicitCity;
import org.hl7.v3.AdxpExplicitPostalCode;
import org.hl7.v3.AdxpExplicitState;
import org.hl7.v3.AdxpExplicitStreetAddressLine;
import org.hl7.v3.CE;
import org.hl7.v3.COCTMT090003UV01AssignedEntity;
import org.hl7.v3.COCTMT150002UV01Organization;
import org.hl7.v3.EnExplicitFamily;
import org.hl7.v3.EnExplicitGiven;
import org.hl7.v3.II;
import org.hl7.v3.IVLTSExplicit;
import org.hl7.v3.MFMIMT700711UV01Custodian;
import org.hl7.v3.MFMIMT700711UV01QueryAck;
import org.hl7.v3.PNExplicit;
import org.hl7.v3.PRPAIN201305UV02;
import org.hl7.v3.PRPAIN201305UV02QUQIMT021001UV01ControlActProcess;
import org.hl7.v3.PRPAIN201306UV02;
import org.hl7.v3.PRPAIN201306UV02MFMIMT700711UV01ControlActProcess;
import org.hl7.v3.PRPAIN201306UV02MFMIMT700711UV01RegistrationEvent;
import org.hl7.v3.PRPAIN201306UV02MFMIMT700711UV01Subject1;
import org.hl7.v3.PRPAIN201306UV02MFMIMT700711UV01Subject2;
import org.hl7.v3.PRPAMT201306UV02LivingSubjectAdministrativeGender;
import org.hl7.v3.PRPAMT201306UV02LivingSubjectBirthTime;
import org.hl7.v3.PRPAMT201306UV02LivingSubjectId;
import org.hl7.v3.PRPAMT201306UV02LivingSubjectName;
import org.hl7.v3.PRPAMT201306UV02ParameterList;
import org.hl7.v3.PRPAMT201306UV02PatientAddress;
import org.hl7.v3.PRPAMT201306UV02PatientTelecom;
import org.hl7.v3.PRPAMT201306UV02QueryByParameter;
import org.hl7.v3.PRPAMT201310UV02OtherIDs;
import org.hl7.v3.PRPAMT201310UV02Patient;
import org.hl7.v3.PRPAMT201310UV02Person;
import org.hl7.v3.ParticipationTargetSubject;
import org.hl7.v3.QueryResponse;
import org.hl7.v3.XActMoodIntentEvent;
import org.openhie.openempi.model.Gender;
import org.openhie.openempi.model.IdentifierDomain;
import org.openhie.openempi.model.Person;
import org.openhie.openempi.model.PersonIdentifier;

public class Hl7ConversionHelper {
    private static final Log log = LogFactory.getLog(Hl7ConversionHelper.class);
    private static SimpleDateFormat hl7DateFormat = new SimpleDateFormat("yyyyMMdd");

    private boolean allowAddressQueryAttribute = false;
    private boolean allowTelecomQueryAttribute = false;
    private boolean allowSocialSecurityNumberQueryAttribute = false;
    private boolean allowAddressResponseAttribute = false;
    private boolean allowTelecomResponseAttribute = false;
    private boolean allowSocialSecurityNumberResponseAttribute = false;
    private boolean allowPartialMatches = false;

    private String assigningAuthorityId;
    private String localHomeCommunityId;

    /**
     *    We parse the request message based on the requirements of the NHIN Patient Discovery
     *  Web Service Interface Specification:
     *  
     *  Required values for the Patient Discovery request:
     *  1.LivingSubjectName Parameter: Both family? and given? elements are required.  
     *     If patients are known by multiple names or have had a name change, the alternative names shall be
     *     specified as multiple instances of LivingSubjectName.  Inclusion of all current and former names
     *     increases the likelihood of a correct match but may incur privacy concerns.
     *     The sequence of given names in the given name list reflects the sequence in which they are
     *     known  first, second, third etc. The first name is specified in the first given? element in the list.
     *     Any middle name is specified in the second given? element in the list when there are no more than two
     *     given? elements.
     *  2.LivingSubjectAdministrativeGender Parameter: Is required. Coded using the HL7 coding for AdministrativeGender;
     *     namely code equal to one of M? (Male) or F (Female) or UN? (Undifferentiated).
     *  3.LivingSubjectBirthTime Parameter: Is required. The contents must contain the greatest degree of detail
     *     as is available.
     */
    public Person extractQueryPerson(org.hl7.v3.PRPAIN201305UV02 findCandidatesRequest) {
        Person person = new Person();
        log.debug("Extracting query criteria from message:\n" + findCandidatesRequest);

        if (findCandidatesRequest == null) {
            log.warn("input message was null, no query parameters present in message");
            return null;
        }

        PRPAMT201306UV02ParameterList queryParamList = extractQueryParameterList(findCandidatesRequest);
        if (queryParamList == null) {
            log.warn("Unable to extract query parameter list from input message.");
            return null;
        }

        populatePersonIdentifiers(person, queryParamList);
        populatePersonNameAttributes(person, queryParamList);
        populatePersonGender(person, queryParamList);
        populatePersonBirthDate(person, queryParamList);
        if (allowAddressQueryAttribute) {
            populatePersonAddress(person, queryParamList);
        }
        if (allowTelecomQueryAttribute) {
            populatePersonPhoneNumber(person, queryParamList);
        }
        return person;
    }

    /**
     * Required values for the Patient Discovery response:
     * 1.Person.name element: Both family? and given? elements are required.  If patients are known by multiple
     *       names or have had a name change, the alternative names shall be specified as multiple Patient.name elements.
     *       Inclusion of all current and former names increases the likelihood of a correct match. 
     *       The sequence of given names in the given name list reflects the sequence in which they are known  first, second, third etc.
     *       The first name is specified in the first given? element in the list. Any middle name is specified in the second given?
     *       element in the list when there are no more than two given? elements.
     * 2.Person.AdministrativeGenderCode element: Is required.  Coded using the HL7 coding for AdministrativeGender;
     *       namely code equal to one of M? (Male) or F (Female) or UN? (Undifferentiated).
     * 3.Person.BirthTime Parameter: Is required.  The contents must contain the greatest degree of detail as is available.
     * 
     * Required values if available and if allowed for the Patient Discovery Request and Response:
     * 1.Address  The streetAddressLine?, city?, state?, postalCode? shall be used for elements of the address.
     *       Multiple streetAddressLine? elements may be used if necessary and are specified in order of appearance in the address.
     *       For more information about coding of addresses see 
     *       htp://www.hl7.org/v3ballot/html/infrastructure/datatypes/datatypes.htm#prop-AD.formatted
     * 2.PatientTelecom  a single phone number. See section 3.1.5.1 for details regarding coding of phone numbers.
     * 3.Social Security Number  SSN is specified in a LivingSubjectId element  potentially one of several.
     *       When specified within the response, the SSN is specified in an OtherIDs element.
     *       SSN is designated using the OID 2.16.840.1.113883.4.1.
     * 
     * @param candidates
     * @return
     */
    public PRPAIN201306UV02 generateResponseMessage(List<Person> candidates, PRPAIN201305UV02 request,
            boolean isSystemError) {
        log.debug("Begin generateResponseMesssage");
        PRPAIN201306UV02 message = new PRPAIN201306UV02();
        buildMessageTransmissionWrapper(request, message, AcknowledgementType.AA.value());
        buildControlActProcess(candidates, request, message, isSystemError);
        return message;
    }

    private void buildControlActProcess(List<Person> candidates, PRPAIN201305UV02 request, PRPAIN201306UV02 message,
            boolean isSystemError) {
        message.setControlActProcess(
                generateMFMIMT700711UV01ControlActProcess(candidates, request, message, isSystemError));
    }

    /**
     * The IHE Patient Discovery Specification indicates the following response codes should be used based on
     * the five different response conditions identified by the specification:
     * 
     * The Initiating Gateway Actor shall act on the query response as described by the following 5
     * cases:
     * Case 1: The Responding Gateway Actor finds exactly one patient record matching the criteria
     * sent in the query parameters.
     * AA (application accept) is returned in Acknowledgement.typeCode (transmission wrapper).
     * OK (data found, no errors) is returned in QueryAck.queryResponseCode (control act wrapper)
     * One RegistrationEvent (and the associated Patient role, subject of that event) is returned from the
     * patient information source for the patient record found. The community associated with the
     * Initiating Gateway may use the patient demographics and identifiers to: a) run an independent
     * matching algorithm to ensure the quality of the match b) use the designated patient identifier in a
     * Cross Gateway Query to get information about records related to the patient c) cache the
     * correlation for future use (see ITI TF-2b: 3.55.4.2.3.1 for more information about caching) d)
     * use a Patient Location Query transaction to get a list of patient data locations.
     * 
     * Case 2: The Responding Gateway Actor finds more than one patient close to matching the
     * criteria sent in the query parameters and the policy allows returning multiple.
     * AA (application accept) is returned in Acknowledgement.typeCode (transmission wrapper).
     * OK (data found, no errors) is returned in QueryAck.queryResponseCode (control act wrapper)
     * One RegistrationEvent (and the associated Patient role, subject of that event) is returned for each
     * patient record found. The community associated with the Initiating Gateway may run its own
     * matching algorithm to select from the list of returned patients. If a correlation is found the
     * Responding Gateway may continue as if only one entry had been returned. If a correlation is still
     * not clear it is expected that human intervention is required, depending on the policies of the
     * Initiating Gateways community.
     * 
     * Case 3: The Responding Gateway Actor finds more than one patient close to matching the
     * criteria sent in the query parameters but no matches close enough for the necessary assurance
     * level and more attributes might allow the Responding Gateway to return a match.
     * AA (application accept) is returned in Acknowledgement.typeCode (transmission wrapper).
     * OK (data found, no errors) is returned in QueryAck.queryResponseCode (control act wrapper)
     * No RegistrationEvent is returned in the response, but the Responding Gateway provides a
     * suggestion in terms of demographics that may help identify a match. The mechanism for
     * specifying the suggestion is detailed in ITI TF-2b: 3.55.4.2.2.6 for description of coding of the
     * response. The Initiating Gateway may use this feedback to initiate a new Cross Gateway Patient
     * Discovery request including the requested additional attributes.
     * 
     * Case 4: The Responding Gateway Actor finds no patients anywhere close to matching the
     * criteria sent in the query parameters.
     * AA (application accept) is returned in Acknowledgement.typeCode (transmission wrapper).
     * OK (data found, no errors) is returned in QueryAck.queryResponseCode (control act wrapper)
     * There is no RegistrationEvent returned in the response. The Initiating Gateway can assume this
     * patient has no healthcare information held by the community represented by the Responding
     * Gateway. This lack of correlation may be cached, see ITI TF-2b: 3.55.4.2.3.1 for more
     * information about caching.
     * 
     * Case 5: The Responding Gateway Actor is unable to satisfy the request. This may be because
     * the request came synchronously and an asynchronous request may be feasible, or because the
     * Responding Gateway Actor is overloaded with other requests and does not expect to answer for a
     * significant period of time. Or the Responding Gateway may need some manuel configuration
     * update to authorize responder.
     * AA (application accept) is returned in Acknowledgement.typeCode (transmission wrapper).
     * QE (application error) is returned in QueryAck.queryResponseCode (control act wrapper)
     * There is no RegistrationEvent returned in the response. See ITI TF-2b: 3.55.4.2.2.7 for more
     * information about coding errors for this case.
     */
    private PRPAIN201306UV02MFMIMT700711UV01ControlActProcess generateMFMIMT700711UV01ControlActProcess(
            List<Person> candidates, PRPAIN201305UV02 request, PRPAIN201306UV02 message, boolean isSystemError) {
        PRPAIN201306UV02MFMIMT700711UV01ControlActProcess controlActProcess = new PRPAIN201306UV02MFMIMT700711UV01ControlActProcess();
        controlActProcess.setMoodCode(XActMoodIntentEvent.EVN);
        controlActProcess.setClassCode(ActClassControlAct.CACT);
        controlActProcess.setCode(Utilities.generateCd(ConversionConstants.PRPA_TE201306UV02,
                ConversionConstants.INTERACTION_ID_ROOT));

        for (Person person : candidates) {
            List<PersonIdentifier> uniqueIdentifiers = extractUniqueIdentifiers(person.getPersonIdentifiers());
            for (PersonIdentifier identifier : uniqueIdentifiers) {
                controlActProcess.getSubject().add(createSubjectFromPerson(person, identifier, request));
            }
        }

        controlActProcess.setQueryAck(createQueryAck(request, isSystemError));

        // Add in query parameters
        if (request.getControlActProcess() != null && request.getControlActProcess().getQueryByParameter() != null
                && request.getControlActProcess().getQueryByParameter().getValue() != null) {
            controlActProcess.setQueryByParameter(request.getControlActProcess().getQueryByParameter());
        }
        return controlActProcess;
    }

    private MFMIMT700711UV01QueryAck createQueryAck(PRPAIN201305UV02 request, boolean isSystemError) {
        MFMIMT700711UV01QueryAck result = new MFMIMT700711UV01QueryAck();

        if (request.getControlActProcess() != null && request.getControlActProcess().getQueryByParameter() != null
                && request.getControlActProcess().getQueryByParameter().getValue() != null
                && request.getControlActProcess().getQueryByParameter().getValue().getQueryId() != null) {
            result.setQueryId(request.getControlActProcess().getQueryByParameter().getValue().getQueryId());
        }

        String responseCode = QueryResponse.OK.name();
        if (isSystemError) {
            responseCode = QueryResponse.QE.name();
        }
        result.setQueryResponseCode(Utilities.generateCs(responseCode));

        return result;
    }

    /**
     * Eliminates the SSN from the list of person identifiers if present since it should be 
     * added to the response message as an asOtherId entry.
     * 
     * @param personIdentifiers
     * @return
     */
    private List<PersonIdentifier> extractUniqueIdentifiers(Set<PersonIdentifier> personIdentifiers) {
        List<PersonIdentifier> uniqueIds = new java.util.ArrayList<PersonIdentifier>();
        for (PersonIdentifier identifier : personIdentifiers) {
            if (identifier.getIdentifierDomain() != null
                    && identifier.getIdentifierDomain().getNamespaceIdentifier() != null
                    && identifier.getIdentifierDomain().getNamespaceIdentifier()
                            .equalsIgnoreCase(ConversionConstants.SSN_ID_ROOT)) {
                continue;
            }
            uniqueIds.add(identifier);
        }
        return uniqueIds;
    }

    private void buildMessageTransmissionWrapper(PRPAIN201305UV02 request, PRPAIN201306UV02 message,
            String acknowledmentCode) {
        message.setITSVersion("XML_1.0");
        message.setId(Utilities.generateHl7MessageId(getLocalHomeCommunityId()));
        message.setCreationTime(Utilities.generateCreationTime());
        message.setInteractionId(Utilities.generateHl7Id(ConversionConstants.INTERACTION_ID_ROOT,
                ConversionConstants.PRPA_IN201306UV02));
        message.setProcessingCode(Utilities.generateCs(ConversionConstants.PROCESSING_CODE_PRODUCTION));
        message.setProcessingModeCode(Utilities.generateCs(ConversionConstants.PROCESSING_MODE_CODE_INITIAL_LOAD));
        message.setAcceptAckCode(Utilities.generateCs(ConversionConstants.ACCEPT_ACK_CODE_NEVER));
        message.getReceiver().add(Utilities.generateMCCIMT00300UV01Receiver(request.getSender()));
        message.setSender(Utilities.generateMCCIMT00300UV01Sender(request.getReceiver()));
        message.getAcknowledgement().add(Utilities.generatedAcknowledgment(request.getId(), acknowledmentCode));
    }

    private PRPAIN201306UV02MFMIMT700711UV01Subject1 createSubjectFromPerson(Person person,
            PersonIdentifier identifier, PRPAIN201305UV02 request) {
        PRPAIN201306UV02MFMIMT700711UV01Subject1 subject = new PRPAIN201306UV02MFMIMT700711UV01Subject1();
        subject.getTypeCode().add(ConversionConstants.ACT_RELATIONSHIP_TYPE);
        PRPAIN201306UV02MFMIMT700711UV01RegistrationEvent regEvent = new PRPAIN201306UV02MFMIMT700711UV01RegistrationEvent();
        regEvent.getClassCode().add(ConversionConstants.CLASS_CODE_REGISTRATION_EVENT);
        regEvent.getMoodCode().add(ConversionConstants.MOOD_CODE_EVENT);
        regEvent.getId().add(Utilities.generateHl7Id(null, null, ConversionConstants.NULL_FLAVOR));
        regEvent.setStatusCode(Utilities.generateCs(ConversionConstants.STATUS_CODE_ACTIVE));
        regEvent.setSubject1(createSubject2FromPerson(person, identifier, request));
        regEvent.setCustodian(createCustodian(person, identifier));
        subject.setRegistrationEvent(regEvent);
        return subject;
    }

    private PRPAIN201306UV02MFMIMT700711UV01Subject2 createSubject2FromPerson(Person person,
            PersonIdentifier identifier, PRPAIN201305UV02 request) {
        PRPAIN201306UV02MFMIMT700711UV01Subject2 subject = new PRPAIN201306UV02MFMIMT700711UV01Subject2();
        subject.setTypeCode(ParticipationTargetSubject.SBJ);
        PRPAMT201310UV02Patient patient = new PRPAMT201310UV02Patient();
        patient.getClassCode().add(ConversionConstants.CLASS_CODE_PATIENT);
        patient.getId().add(generateIdFromPersonIdentifier(identifier));
        patient.setStatusCode(Utilities.generateCs(ConversionConstants.STATUS_CODE_ACTIVE));
        patient.setPatientPerson(createPatientPersonFromPerson(person, request));
        subject.setPatient(patient);
        return subject;
    }

    private JAXBElement<PRPAMT201310UV02Person> createPatientPersonFromPerson(Person person,
            PRPAIN201305UV02 request) {
        PRPAMT201310UV02Person thePerson = new PRPAMT201310UV02Person();
        PNExplicit name = Utilities.generatePnExplicit(person.getGivenName(), person.getFamilyName());
        thePerson.getName().add(name);
        thePerson
                .setAdministrativeGenderCode(Utilities.generateCe(getAdministrativeGenderCode(person.getGender())));
        thePerson.setBirthTime(Utilities.generateTSExplicit(person.getDateOfBirth()));

        // If the adapter is configured to return the phone number and we have, it add it to the message.
        if (allowTelecomResponseAttribute && person.getPhoneNumber() != null) {
            thePerson.getTelecom().add(Utilities.generateTELExplicit(person));
        }

        // If the adapter is configured to return the address and we have it, add it to the message
        if (allowAddressResponseAttribute) {
            ADExplicit address = Utilities.generateADExplicit(person.getAddress1(), person.getCity(),
                    person.getState(), person.getPostalCode());
            if (address != null) {
                thePerson.getAddr().add(address);
            }
        }

        if (allowSocialSecurityNumberResponseAttribute && Utilities.isNotNullish(person.getSsn())) {
            thePerson.getAsOtherIDs().add(generateSsnAsOtherId(person));
        }

        javax.xml.namespace.QName xmlqname = new javax.xml.namespace.QName("urn:hl7-org:v3", "patientPerson");
        JAXBElement<PRPAMT201310UV02Person> jaxbPerson = new JAXBElement<PRPAMT201310UV02Person>(xmlqname,
                PRPAMT201310UV02Person.class, thePerson);

        return jaxbPerson;
    }

    private PRPAMT201310UV02OtherIDs generateSsnAsOtherId(Person person) {
        PRPAMT201310UV02OtherIDs otherIds = new PRPAMT201310UV02OtherIDs();

        otherIds.getClassCode().add(ConversionConstants.AS_OTHER_IDS_SSN_CLASS_CODE);

        // Set the SSN
        II ssn = new II();
        ssn.setExtension(person.getSsn());
        ssn.setRoot(ConversionConstants.SSN_ID_ROOT);
        otherIds.getId().add(ssn);

        COCTMT150002UV01Organization scopingOrg = new COCTMT150002UV01Organization();
        scopingOrg.setClassCode(ConversionConstants.CLASS_CODE_ORG);
        scopingOrg.setDeterminerCode(ConversionConstants.DETERMINER_CODE_INSTANCE);
        II orgId = new II();
        orgId.setRoot(ssn.getRoot());
        scopingOrg.getId().add(orgId);
        otherIds.setScopingOrganization(scopingOrg);

        return otherIds;
    }

    private String getAdministrativeGenderCode(Gender gender) {
        String code = null;
        if (gender == null || gender.getGenderCode() == null) {
            return null;
        }

        String value = gender.getGenderCode();
        if (value.equalsIgnoreCase(ConversionConstants.OPENEMPI_MALE_GENDER_CODE)) {
            code = ConversionConstants.MALE_GENDER_CODE;
        } else if (value.equalsIgnoreCase(ConversionConstants.OPENEMPI_FEMALE_GENDER_CODE)) {
            code = ConversionConstants.FEMALE_GENDER_CODE;
        } else if (value.equalsIgnoreCase(ConversionConstants.OPENEMPI_UNDIFFERENTIATED_GENDER_CODE)) {
            code = ConversionConstants.UNDIFFERENTIATED_GENDER_CODE;
        }
        log.debug("Looking up gender code for value: " + value + " resulted in: " + code);
        return code;
    }

    private MFMIMT700711UV01Custodian createCustodian(Person person, PersonIdentifier identifier) {
        MFMIMT700711UV01Custodian result = new MFMIMT700711UV01Custodian();
        result.getTypeCode().add(ConversionConstants.PARTICIPATION_CUSTODIAN);

        result.setAssignedEntity(createAssignedEntity(identifier));

        return result;
    }

    private static COCTMT090003UV01AssignedEntity createAssignedEntity(PersonIdentifier identifier) {
        COCTMT090003UV01AssignedEntity assignedEntity = new COCTMT090003UV01AssignedEntity();

        II id = new II();
        String idRoot = null;
        if (identifier.getIdentifierDomain() != null
                && identifier.getIdentifierDomain().getNamespaceIdentifier() != null) {
            idRoot = identifier.getIdentifierDomain().getNamespaceIdentifier();
        } else if (identifier.getIdentifierDomain() != null
                && identifier.getIdentifierDomain().getUniversalIdentifier() != null) {
            idRoot = identifier.getIdentifierDomain().getUniversalIdentifier();
        }

        if (idRoot != null) {
            id.setRoot(idRoot);
        }
        assignedEntity.setCode(Utilities.generateCe(ConversionConstants.NO_HEALTH_DATA_LOCATOR_CODE,
                ConversionConstants.NO_HEALTH_DATA_LOCATOR_CODE_SYSTEM));
        assignedEntity.setClassCode(ConversionConstants.ROLE_CLASS_ASSIGNED);
        assignedEntity.getId().add(id);

        return assignedEntity;
    }

    private II generateIdFromPersonIdentifier(PersonIdentifier identifier) {
        II id = new II();
        id.setExtension(identifier.getIdentifier());

        String idRoot = null;
        if (identifier.getIdentifierDomain() != null
                && identifier.getIdentifierDomain().getNamespaceIdentifier() != null) {
            idRoot = identifier.getIdentifierDomain().getNamespaceIdentifier();
        } else if (identifier.getIdentifierDomain() != null
                && identifier.getIdentifierDomain().getUniversalIdentifier() != null) {
            idRoot = identifier.getIdentifierDomain().getUniversalIdentifier();
        }

        if (idRoot != null) {
            id.setRoot(idRoot);
        }
        return id;
    }

    private void populatePersonIdentifiers(Person person, PRPAMT201306UV02ParameterList queryParamList) {
        log.debug("Entering Hl7ConversionHelper.populatePersonIdentifiers method...");

        //Extract the patient identifiers in the requesting message
        if (!hasLivingSubjectIds(queryParamList)) {
            log.warn("Message does not include a living subject ID.");
            return;
        }

        List<PRPAMT201306UV02LivingSubjectId> idList = queryParamList.getLivingSubjectId();
        for (PRPAMT201306UV02LivingSubjectId id : idList) {
            if (!hasLivingSubjectIdValue(id)) {
                log.warn("Message does not include a living subject ID value.");
                continue;
            }

            II identifier = id.getValue().get(0);
            // If this is the SSN then it requires special treatment within OpenEMPI
            // The following text below is quoted from the NHIN Patient Discovery Specification
            // Social Security Number  SSN is specified in a LivingSubjectId element  potentially one of several. 
            // SSN is designated using the OID 2.16.840.1.113883.4.1
            //
            if ((identifier.getRoot().equalsIgnoreCase(ConversionConstants.SSN_OID)
                    && allowSocialSecurityNumberQueryAttribute)) {
                log.debug("Message includes the SSN as a query attribute.");
                person.setSsn(identifier.getExtension());
                continue;
            }

            // If the identifier is not the SSN then it is the Patient ID assigned to the patient by the initiating gateway
            IdentifierDomain domain = new IdentifierDomain();
            domain.setUniversalIdentifier(identifier.getRoot());
            domain.setNamespaceIdentifier(identifier.getRoot());
            PersonIdentifier personIdentifier = new PersonIdentifier();
            personIdentifier.setIdentifier(identifier.getExtension());
            personIdentifier.setIdentifierDomain(domain);
            person.addPersonIdentifier(personIdentifier);
            log.debug("Added person identifier of: " + personIdentifier);
        }
    }

    private void populatePersonBirthDate(Person person, PRPAMT201306UV02ParameterList queryParamList) {
        log.debug("Entering Hl7ConversionHelper.populatePersonIdentifiers.populatePersonBirthDate method...");

        // Extract the birth time from the query parameters - Assume only one was specified
        if (!hasLivingSubjectBirthTime(queryParamList)) {
            log.warn("Message does not include a living subject birth time.");
            return;
        }

        PRPAMT201306UV02LivingSubjectBirthTime birthTime = queryParamList.getLivingSubjectBirthTime().get(0);
        if (!hasLivingSubjectBirthTimeValue(birthTime)) {
            log.warn("Message does not include a valid living subject birth time value.");
            return;
        }

        IVLTSExplicit birthday = birthTime.getValue().get(0);
        log.debug("Found birthTime in query parameters = " + birthday.getValue());

        try {
            java.util.Date dob = hl7DateFormat.parse(birthday.getValue());
            log.debug("Extracted dob = " + dob.toString());
            person.setDateOfBirth(dob);
        } catch (Exception ex) {
            log.warn("Message does not include a valid living subject birth time value.");
        }
    }

    /**
     * LivingSubjectAdministrativeGender Parameter: Is required. Coded using the HL7 coding for
     * AdministrativeGender; namely code equal to one of M? (Male) or F (Female) or UN?
     * (Undifferentiated).
     * 
     * @param person
     * @param queryParamList
     */
    private void populatePersonGender(Person person, PRPAMT201306UV02ParameterList queryParamList) {
        // Extract the gender from the query parameters - Assume only one was specified
        if (!hasLivingSubjectAdministrativeGender(queryParamList)) {
            log.warn("Message does not include a living subject admistrative gender code.");
            return;
        }

        PRPAMT201306UV02LivingSubjectAdministrativeGender adminGender = queryParamList
                .getLivingSubjectAdministrativeGender().get(0);
        if (!hasLivingSubjectAdministrativeGenderCodeValue(adminGender)) {
            log.warn("Message does not include a living subject admistrative gender code value.");
            return;
        }

        CE administrativeGenderCode = adminGender.getValue().get(0);
        String value = administrativeGenderCode.getCode();
        log.debug("Found living subject administrative gender code value of " + value);
        if (value.equalsIgnoreCase(ConversionConstants.MALE_GENDER_CODE)) {
            Gender gender = new Gender();
            gender.setGenderCode(ConversionConstants.OPENEMPI_MALE_GENDER_CODE);
            person.setGender(gender);
        } else if (value.equalsIgnoreCase(ConversionConstants.FEMALE_GENDER_CODE)) {
            Gender gender = new Gender();
            gender.setGenderCode(ConversionConstants.OPENEMPI_FEMALE_GENDER_CODE);
            person.setGender(gender);
        } else if (value.equalsIgnoreCase(ConversionConstants.UNDIFFERENTIATED_GENDER_CODE)) {
            Gender gender = new Gender();
            gender.setGenderCode(ConversionConstants.OPENEMPI_UNDIFFERENTIATED_GENDER_CODE);
            person.setGender(gender);
        } else {
            log.warn("Found unknown living subject administrative gender code value of " + value);
        }
    }

    @SuppressWarnings("unchecked")
    private void populatePersonAddress(Person person, PRPAMT201306UV02ParameterList queryParamList) {
        // Extract the address of the person from the query parameters - This is an optional attribute
        // so it may not be present in the message
        if (!hasPatientAddress(queryParamList)) {
            log.debug("Message does not include the address as a query criterion.");
            return;
        }
        PRPAMT201306UV02PatientAddress address = queryParamList.getPatientAddress().get(0);

        if (!hasPatientAddressValue(address)) {
            log.debug("Message does not include the address value as a query criterion.");
            return;
        }

        List<Serializable> addressValue = address.getValue().get(0).getContent();

        String nameString = "";
        for (Iterator<Serializable> iterSerialObjects = addressValue.iterator(); iterSerialObjects.hasNext();) {
            log.info("in iterSerialObjects.hasNext() loop");
            Serializable contentItem = iterSerialObjects.next();
            if (contentItem instanceof String) {
                log.debug("contentItem is string");
                String strValue = (String) contentItem;

                if (nameString != null) {
                    nameString += strValue;
                } else {
                    nameString = strValue;
                }
                log.debug("nameString=" + nameString);
            } else if (contentItem instanceof JAXBElement) {
                log.debug("contentItem is JAXBElement");
                JAXBElement oJAXBElement = (JAXBElement) contentItem;
                log.debug("found element of type: " + oJAXBElement.getValue().getClass());
                if (oJAXBElement.getValue() instanceof AdxpExplicitStreetAddressLine) {
                    AdxpExplicitStreetAddressLine addressLine = (AdxpExplicitStreetAddressLine) oJAXBElement
                            .getValue();
                    if (addressLine.getContent() != null) {
                        log.debug("found Address Line element; content=" + addressLine.getContent());
                        person.setAddress1(addressLine.getContent());
                    }
                } else if (oJAXBElement.getValue() instanceof AdxpExplicitCity) {
                    AdxpExplicitCity city = (AdxpExplicitCity) oJAXBElement.getValue();
                    if (city.getContent() != null) {
                        log.debug("found city element; content=" + city.getContent());
                        person.setCity(city.getContent());
                    }
                } else if (oJAXBElement.getValue() instanceof AdxpExplicitState) {
                    AdxpExplicitState state = (AdxpExplicitState) oJAXBElement.getValue();
                    if (state.getContent() != null) {
                        log.debug("found state element; content=" + state.getContent());
                        person.setState(state.getContent());
                    }
                } else if (oJAXBElement.getValue() instanceof AdxpExplicitPostalCode) {
                    AdxpExplicitPostalCode postalCode = (AdxpExplicitPostalCode) oJAXBElement.getValue();
                    if (postalCode.getContent() != null) {
                        log.debug("found postalCode element; content=" + postalCode.getContent());
                        person.setPostalCode(postalCode.getContent());
                    }
                } else {
                    log.warn("other name part=" + oJAXBElement.getValue());
                }
            } else {
                log.info("contentItem is other");
            }

        }
    }

    private void populatePersonPhoneNumber(Person person, PRPAMT201306UV02ParameterList queryParamList) {
        // Extract the phone number of the person from the query parameters - This is an optional attribute
        // so it may not be present in the message
        if (!hasPatientPhoneNumber(queryParamList)) {
            log.debug("Message does not include the phone number as a query criterion.");
            return;
        }
        PRPAMT201306UV02PatientTelecom phoneNumber = queryParamList.getPatientTelecom().get(0);

        if (!hasPatientPhoneNumberValue(phoneNumber)) {
            log.debug("Message does not include the phone number value as a query criterion.");
            return;
        }

        String value = phoneNumber.getValue().get(0).getValue();
        log.debug("Found patientTelecom in query parameters = " + value);
        if (value.startsWith(ConversionConstants.TELECOM_URL_SCHEME)) {
            person.setPhoneNumber(value.substring(value.indexOf(ConversionConstants.TELECOM_URL_SCHEME)
                    + ConversionConstants.TELECOM_URL_SCHEME.length()));
        } else {
            person.setPhoneNumber(value);
        }
    }

    @SuppressWarnings("unchecked")
    private void populatePersonNameAttributes(Person person, PRPAMT201306UV02ParameterList params) {
        log.debug("Populating person name attributes from name.");

        // Extract the name from the query parameters - Assume only one was
        // specified
        if (!hasLivingSubjectName(params)) {
            log.warn("Message does not include a living subject name: " + params);
            return;
        }
        PRPAMT201306UV02LivingSubjectName name = params.getLivingSubjectName().get(0);

        if (!hasLivingSubjectNameValue(name)) {
            log.warn("Message does not include a living subject name: " + params);
            return;
        }

        List<Serializable> choice = name.getValue().get(0).getContent();

        String nameString = "";
        int givenNameIndex = 0;
        EnExplicitGiven givenName = null;
        EnExplicitGiven middleName = null;
        EnExplicitFamily familyName = null;
        for (Iterator<Serializable> iterSerialObjects = choice.iterator(); iterSerialObjects.hasNext();) {
            log.info("in iterSerialObjects.hasNext() loop");
            Serializable contentItem = iterSerialObjects.next();

            if (contentItem instanceof String) {
                log.debug("contentItem is string");
                String strValue = (String) contentItem;

                if (nameString != null) {
                    nameString += strValue;
                } else {
                    nameString = strValue;
                }
                log.debug("nameString=" + nameString);
            } else if (contentItem instanceof JAXBElement) {
                log.debug("contentItem is JAXBElement");
                JAXBElement oJAXBElement = (JAXBElement) contentItem;
                if (oJAXBElement.getValue() instanceof EnExplicitFamily) {
                    familyName = (EnExplicitFamily) oJAXBElement.getValue();
                    log.debug("found lastname element; content=" + familyName.getContent());
                } else if (oJAXBElement.getValue() instanceof EnExplicitGiven) {
                    if (givenNameIndex == 0) {
                        givenName = (EnExplicitGiven) oJAXBElement.getValue();
                        log.info("found firstname element; content=" + givenName.getContent());
                        givenNameIndex++;
                    } else {
                        middleName = (EnExplicitGiven) oJAXBElement.getValue();
                        log.info("found middlename element; content=" + givenName.getContent());
                        givenNameIndex++;
                    }
                } else {
                    log.warn("other name part=" + oJAXBElement.getValue());
                }
            } else {
                log.info("contentItem is other");
            }
        }

        if ((familyName != null && familyName.getContent() != null)
                || (givenName != null && givenName.getContent() != null)
                || (middleName != null && middleName.getContent() != null)) {

            if (familyName != null && familyName.getContent() != null) {
                person.setFamilyName(familyName.getContent());
            }

            if (givenName != null && givenName.getContent() != null) {
                person.setGivenName(givenName.getContent());
            }

            if (middleName != null && middleName.getContent() != null) {
                person.setMiddleName(middleName.getContent());
            }
        } else {

            if (nameString.length() > 0) {
                person.setFamilyName(nameString);
            }
        }
        log.debug("Added person name attributes to the query person: " + person);
    }

    private boolean hasLivingSubjectIds(PRPAMT201306UV02ParameterList params) {
        return params.getLivingSubjectId() != null && params.getLivingSubjectId().size() > 0
                && params.getLivingSubjectId().get(0) != null;
    }

    private boolean hasLivingSubjectIdValue(PRPAMT201306UV02LivingSubjectId id) {
        return id.getValue() != null && id.getValue().size() > 0 && id.getValue().get(0) != null
                && id.getValue().get(0).getExtension() != null && id.getValue().get(0).getRoot() != null;
    }

    private static boolean hasLivingSubjectBirthTimeValue(PRPAMT201306UV02LivingSubjectBirthTime birthTime) {
        return birthTime.getValue() != null && birthTime.getValue().size() > 0
                && birthTime.getValue().get(0) != null;
    }

    private boolean hasLivingSubjectAdministrativeGenderCodeValue(
            PRPAMT201306UV02LivingSubjectAdministrativeGender gender) {
        return gender.getValue() != null && gender.getValue().size() > 0 && gender.getValue().get(0) != null;
    }

    private static boolean hasLivingSubjectBirthTime(PRPAMT201306UV02ParameterList params) {
        return params.getLivingSubjectBirthTime() != null && params.getLivingSubjectBirthTime().size() > 0
                && params.getLivingSubjectBirthTime().get(0) != null;
    }

    private boolean hasPatientAddress(PRPAMT201306UV02ParameterList params) {
        return params.getPatientAddress() != null && params.getPatientAddress().size() > 0
                && params.getPatientAddress().get(0) != null;
    }

    private boolean hasPatientAddressValue(PRPAMT201306UV02PatientAddress address) {
        return address.getValue() != null && address.getValue().size() > 0 && address.getValue().get(0) != null;
    }

    private boolean hasPatientPhoneNumber(PRPAMT201306UV02ParameterList params) {
        return params.getPatientTelecom() != null && params.getPatientTelecom().size() > 0
                && params.getPatientTelecom().get(0) != null;
    }

    private boolean hasPatientPhoneNumberValue(PRPAMT201306UV02PatientTelecom phoneNumber) {
        return phoneNumber.getValue() != null && phoneNumber.getValue().size() > 0
                && phoneNumber.getValue().get(0) != null;
    }

    private static boolean hasLivingSubjectAdministrativeGender(PRPAMT201306UV02ParameterList params) {
        return params.getLivingSubjectAdministrativeGender() != null
                && params.getLivingSubjectAdministrativeGender().size() > 0
                && params.getLivingSubjectAdministrativeGender().get(0) != null;
    }

    private static boolean hasLivingSubjectNameValue(PRPAMT201306UV02LivingSubjectName name) {
        return name.getValue() != null && name.getValue().size() > 0 && name.getValue().get(0) != null;
    }

    private static boolean hasLivingSubjectName(PRPAMT201306UV02ParameterList params) {
        return params.getLivingSubjectName() != null && params.getLivingSubjectName().size() > 0
                && params.getLivingSubjectName().get(0) != null;
    }

    private PRPAMT201306UV02ParameterList extractQueryParameterList(
            org.hl7.v3.PRPAIN201305UV02 findCandidatesRequest) {
        PRPAMT201306UV02ParameterList queryParamList = null;
        PRPAIN201305UV02QUQIMT021001UV01ControlActProcess controlActProcess = findCandidatesRequest
                .getControlActProcess();
        if (controlActProcess == null) {
            log.info("controlActProcess is null - no query parameters present in message");
            return null;
        }

        if (controlActProcess.getQueryByParameter() != null
                && controlActProcess.getQueryByParameter().getValue() != null) {
            PRPAMT201306UV02QueryByParameter queryParams = (PRPAMT201306UV02QueryByParameter) controlActProcess
                    .getQueryByParameter().getValue();

            if (queryParams.getParameterList() != null) {
                queryParamList = queryParams.getParameterList();
            }

        }
        return queryParamList;
    }

    public static II buildII(String root, String extension, String assigningAuthorityName) {
        II ii = new II();
        if (Utilities.isNotNullish(root)) {
            log.debug("Setting root attribute of II to " + root);
            ii.setRoot(root);
        }
        if (Utilities.isNotNullish(extension)) {
            log.debug("Setting extension attribute of II to " + extension);
            ii.setExtension(extension);
        }
        if (Utilities.isNotNullish(assigningAuthorityName)) {
            log.debug("Setting assigning authority attribute of II to " + assigningAuthorityName);
            ii.setAssigningAuthorityName(assigningAuthorityName);
        }
        return ii;
    }

    public String getAssigningAuthorityId() {
        return assigningAuthorityId;
    }

    public void setAssigningAuthorityId(String assigningAuthorityId) {
        this.assigningAuthorityId = assigningAuthorityId;
    }

    public String getLocalHomeCommunityId() {
        return localHomeCommunityId;
    }

    public void setLocalHomeCommunityId(String localHomeCommunityId) {
        this.localHomeCommunityId = localHomeCommunityId;
    }

    public boolean isAllowAddressQueryAttribute() {
        return allowAddressQueryAttribute;
    }

    public void setAllowAddressQueryAttribute(boolean allowAddressQueryAttribute) {
        this.allowAddressQueryAttribute = allowAddressQueryAttribute;
    }

    public boolean isAllowTelecomQueryAttribute() {
        return allowTelecomQueryAttribute;
    }

    public void setAllowTelecomQueryAttribute(boolean allowTelecomQueryAttribute) {
        this.allowTelecomQueryAttribute = allowTelecomQueryAttribute;
    }

    public boolean isAllowSocialSecurityNumberQueryAttribute() {
        return allowSocialSecurityNumberQueryAttribute;
    }

    public void setAllowSocialSecurityNumberQueryAttribute(boolean allowSocialSecurityNumberQueryAttribute) {
        this.allowSocialSecurityNumberQueryAttribute = allowSocialSecurityNumberQueryAttribute;
    }

    public boolean isAllowAddressResponseAttribute() {
        return allowAddressResponseAttribute;
    }

    public void setAllowAddressResponseAttribute(boolean allowAddressResponseAttribute) {
        this.allowAddressResponseAttribute = allowAddressResponseAttribute;
    }

    public boolean isAllowTelecomResponseAttribute() {
        return allowTelecomResponseAttribute;
    }

    public void setAllowTelecomResponseAttribute(boolean allowTelecomResponseAttribute) {
        this.allowTelecomResponseAttribute = allowTelecomResponseAttribute;
    }

    public boolean isAllowSocialSecurityNumberResponseAttribute() {
        return allowSocialSecurityNumberResponseAttribute;
    }

    public void setAllowSocialSecurityNumberResponseAttribute(boolean allowSocialSecurityNumberResponseAttribute) {
        this.allowSocialSecurityNumberResponseAttribute = allowSocialSecurityNumberResponseAttribute;
    }

    public boolean isAllowPartialMatches() {
        return allowPartialMatches;
    }

    public void setAllowPartialMatches(boolean allowPartialMatches) {
        this.allowPartialMatches = allowPartialMatches;
    }
}