org.openmrs.module.rheashradapter.util.RsmsNotificationHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.module.rheashradapter.util.RsmsNotificationHandler.java

Source

/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */

package org.openmrs.module.rheashradapter.util;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.Concept;
import org.openmrs.ConceptAnswer;
import org.openmrs.ConceptName;
import org.openmrs.Drug;
import org.openmrs.Encounter;
import org.openmrs.EncounterType;
import org.openmrs.Location;
import org.openmrs.Obs;
import org.openmrs.Patient;
import org.openmrs.PatientIdentifierType;
import org.openmrs.Person;
import org.openmrs.PersonAttribute;
import org.openmrs.PersonAttributeType;
import org.openmrs.PersonName;
import org.openmrs.api.context.Context;
import org.openmrs.hl7.HL7Constants;
import org.openmrs.hl7.handler.ProposingConceptException;
import org.openmrs.module.rheashradapter.api.LogEncounterService;
import org.openmrs.util.OpenmrsConstants;
import org.openmrs.util.OpenmrsUtil;
import org.springframework.util.StringUtils;

import ca.uhn.hl7v2.HL7Exception;
import ca.uhn.hl7v2.app.Application;
import ca.uhn.hl7v2.app.ApplicationException;
import ca.uhn.hl7v2.model.DataTypeException;
import ca.uhn.hl7v2.model.Message;
import ca.uhn.hl7v2.model.Type;
import ca.uhn.hl7v2.model.Varies;
import ca.uhn.hl7v2.model.v25.datatype.CE;
import ca.uhn.hl7v2.model.v25.datatype.CWE;
import ca.uhn.hl7v2.model.v25.datatype.DT;
import ca.uhn.hl7v2.model.v25.datatype.DTM;
import ca.uhn.hl7v2.model.v25.datatype.FT;
import ca.uhn.hl7v2.model.v25.datatype.ID;
import ca.uhn.hl7v2.model.v25.datatype.NM;
import ca.uhn.hl7v2.model.v25.datatype.ST;
import ca.uhn.hl7v2.model.v25.datatype.TM;
import ca.uhn.hl7v2.model.v25.datatype.TS;
import ca.uhn.hl7v2.model.v25.group.ORU_R01_OBSERVATION;
import ca.uhn.hl7v2.model.v25.group.ORU_R01_ORDER_OBSERVATION;
import ca.uhn.hl7v2.model.v25.group.ORU_R01_PATIENT_RESULT;
import ca.uhn.hl7v2.model.v25.message.ORU_R01;
import ca.uhn.hl7v2.model.v25.segment.MSH;
import ca.uhn.hl7v2.model.v25.segment.OBR;
import ca.uhn.hl7v2.model.v25.segment.OBX;
import ca.uhn.hl7v2.model.v25.segment.PID;
import ca.uhn.hl7v2.model.v25.segment.PV1;

public class RsmsNotificationHandler implements Application {

    private String enterpriseId;
    private Log log = LogFactory.getLog(RsmsNotificationHandler.class);
    private LogEncounterService service = Context.getService(LogEncounterService.class);

    public RsmsNotificationHandler(String enterpriseId) {
        super();
        this.enterpriseId = enterpriseId;
    }

    public Message processMessage(Message message) throws ApplicationException {

        if (!(message instanceof ORU_R01))
            throw new ApplicationException("Invalid message sent to ORU_R01 handler");

        log.debug("Processing ORU_R01 message");

        Message response;
        try {
            ORU_R01 oru = (ORU_R01) message;
            response = processORU_R01(oru);
        } catch (ClassCastException e) {
            log.error("Error casting " + message.getClass().getName() + " to ORU_R01", e);
            throw new ApplicationException("Invalid message type for handler");
        } catch (HL7Exception e) {
            log.error("Error while processing ORU_R01 message", e);
            throw new ApplicationException(e);
        }

        log.debug("Finished processing ORU_R01 message");

        return response;
    }

    @SuppressWarnings("deprecation")
    private Message processORU_R01(ORU_R01 oru) throws HL7Exception {

        MSH msh = getMSH(oru);
        PID pid = getPID(oru);
        PV1 pv1 = getPV1(oru);

        Patient patient = getPatient(pid);
        Date encounterDatetime = getEncounterDate();
        Encounter encounter = createEncounter(patient);
        EncounterType encType = getEncounterType(pv1);
        Person provider = getProvider(pv1);
        Location location = getLocation(msh);
        encounter.setEncounterType(encType);
        encounter.setDateCreated(new Date());
        encounter.setProvider(provider);
        encounter.setLocation(location);
        encounter.setEncounterDatetime(encounterDatetime);

        Context.getEncounterService().saveEncounter(encounter);

        encounter = createObs(encounter, oru);

        ORU_R01_PATIENT_RESULT patientResult = oru.getPATIENT_RESULT();
        ORU_R01_ORDER_OBSERVATION orderObs = patientResult.getORDER_OBSERVATION(0);
        OBR obr = orderObs.getOBR();
        obr.getFillerOrderNumber().getEntityIdentifier().setValue(encounter.getId().toString());

        return oru;

    }

    private Encounter createObs(Encounter encounter, ORU_R01 oru) throws HL7Exception {
        ORU_R01_PATIENT_RESULT patientResult = oru.getPATIENT_RESULT();
        MSH msh = getMSH(oru);

        ORU_R01_ORDER_OBSERVATION orderObs = null;
        try {
            orderObs = patientResult.getORDER_OBSERVATION(0);
        } catch (HL7Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        int numObs = orderObs.getOBSERVATIONReps();
        for (int j = 0; j < numObs; j++) {

            OBX obx = orderObs.getOBSERVATION(j).getOBX();
            String messageControlId = msh.getMessageControlID().getValue();
            Obs obs = parseObs(encounter, obx, messageControlId);
            encounter.addObs(obs);
        }

        return encounter;
    }

    private Obs parseObs(Encounter encounter, OBX obx, String uid) throws HL7Exception, ProposingConceptException {
        if (log.isDebugEnabled())
            log.debug("parsing observation: " + obx);
        Varies[] values = obx.getObservationValue();

        // bail out if no values were found
        if (values == null || values.length < 1)
            return null;

        String hl7Datatype = values[0].getName();
        if (log.isDebugEnabled())
            log.debug("  datatype = " + hl7Datatype);
        Concept concept = getConcept(obx.getObservationIdentifier(), uid);
        if (log.isDebugEnabled())
            log.debug("  concept = " + concept.getConceptId());
        ConceptName conceptName = getConceptName(obx.getObservationIdentifier());
        if (log.isDebugEnabled())
            log.debug("  concept-name = " + conceptName);

        Date datetime = getDatetime(obx);
        if (log.isDebugEnabled())
            log.debug("  timestamp = " + datetime);
        if (datetime == null)
            datetime = encounter.getEncounterDatetime();

        Obs obs = new Obs();
        obs.setUuid(UUID.randomUUID().toString());
        obs.setPerson(encounter.getPatient());
        obs.setConcept(concept);
        obs.setEncounter(encounter);
        obs.setObsDatetime(datetime);
        obs.setLocation(encounter.getLocation());
        obs.setCreator(encounter.getCreator());
        obs.setDateCreated(new Date());

        // set comments if there are any
        StringBuilder comments = new StringBuilder();
        ORU_R01_OBSERVATION parent = (ORU_R01_OBSERVATION) obx.getParent();
        // iterate over all OBX NTEs
        for (int i = 0; i < parent.getNTEReps(); i++)
            for (FT obxComment : parent.getNTE(i).getComment()) {
                if (comments.length() > 0)
                    comments.append(" ");
                comments = comments.append(obxComment.getValue());
            }
        // only set comments if there are any
        if (StringUtils.hasText(comments.toString()))
            obs.setComment(comments.toString());

        Type obx5 = values[0].getData();
        if ("NM".equals(hl7Datatype)) {
            String value = ((NM) obx5).getValue();
            if (value == null || value.length() == 0) {
                log.warn("Not creating null valued obs for concept " + concept);
                return null;
            } else if (value.equals("0") || value.equals("1")) {
                concept = concept.hydrate(concept.getConceptId().toString());
                obs.setConcept(concept);
                if (concept.getDatatype().isBoolean())
                    obs.setValueBoolean(value.equals("1"));
                else if (concept.getDatatype().isNumeric())
                    try {
                        obs.setValueNumeric(Double.valueOf(value));
                    } catch (NumberFormatException e) {
                        /*
                        throw new HL7Exception("numeric (NM) value '" + value + "' is not numeric for concept #"
                        + concept.getConceptId() + " (" + conceptName.getName() + ") in message " + uid, e);
                        */}
                else if (concept.getDatatype().isCoded()) {
                    Concept answer = value.equals("1") ? Context.getConceptService().getTrueConcept()
                            : Context.getConceptService().getFalseConcept();
                    boolean isValidAnswer = false;
                    Collection<ConceptAnswer> conceptAnswers = concept.getAnswers();
                    if (conceptAnswers != null && conceptAnswers.size() > 0) {
                        for (ConceptAnswer conceptAnswer : conceptAnswers) {
                            if (conceptAnswer.getAnswerConcept().equals(answer)) {
                                obs.setValueCoded(answer);
                                isValidAnswer = true;
                                break;
                            }
                        }
                    }
                    //answer the boolean answer concept was't found
                    if (!isValidAnswer)
                        throw new HL7Exception(answer.toString() + " is not a valid answer for obs with uuid ");
                } else {
                    //throw this exception to make sure that the handler doesn't silently ignore bad hl7 message
                    throw new HL7Exception("Can't set boolean concept answer for concept with id "
                            + obs.getConcept().getConceptId());
                }
            } else {
                try {
                    obs.setValueNumeric(Double.valueOf(value));
                } catch (NumberFormatException e) {
                    /*throw new HL7Exception("numeric (NM) value '" + value + "' is not numeric for concept #"
                            + concept.getConceptId() + " (" + conceptName.getName() + ") in message " + uid, e);*/
                }
            }
        } else if ("CWE".equals(hl7Datatype)) {
            log.debug("  CWE observation");
            CWE value = (CWE) obx5;
            String valueIdentifier = value.getIdentifier().getValue();
            log.debug("    value id = " + valueIdentifier);
            String valueName = value.getText().getValue();
            log.debug("    value name = " + valueName);
            if (isConceptProposal(valueIdentifier)) {
                if (log.isDebugEnabled())
                    log.debug("Proposing concept");
                throw new ProposingConceptException(concept, valueName);
            } else {
                log.debug("    not proposal");
                try {
                    Concept valueConcept = Context.getConceptService()
                            .getConceptByName(value.getIdentifier().getName().toString());
                    obs.setValueCoded(valueConcept);
                    if (HL7Constants.HL7_LOCAL_DRUG.equals(value.getNameOfAlternateCodingSystem().getValue())) {
                        Drug valueDrug = new Drug();
                        valueDrug.setDrugId(new Integer(value.getAlternateIdentifier().getValue()));
                        obs.setValueDrug(valueDrug);
                    } else {
                        ConceptName valueConceptName = getConceptName(value);
                        if (valueConceptName != null) {
                            if (log.isDebugEnabled()) {
                                log.debug("    value concept-name-id = " + valueConceptName.getConceptNameId());
                                log.debug("    value concept-name = " + valueConceptName.getName());
                            }
                            obs.setValueCodedName(valueConceptName);
                        }
                    }
                } catch (NumberFormatException e) {
                    throw new HL7Exception(
                            "Invalid concept ID '" + valueIdentifier + "' for OBX-5 value '" + valueName + "'");
                }
            }
            if (log.isDebugEnabled())
                log.debug("  Done with CWE");
        } else if ("CE".equals(hl7Datatype)) {
            CE value = (CE) obx5;
            String valueIdentifier = value.getIdentifier().getValue();
            String valueName = value.getText().getValue();

            if (isConceptProposal(valueIdentifier)) {
                /*
                throw new ProposingConceptException(concept, valueName);
                */} else {
                try {

                    Concept c = getConcept(value, uid);
                    obs.setValueCoded(c);
                    ConceptName name = c.getName();
                    obs.setValueCodedName(name);

                } catch (NumberFormatException e) {
                    throw new HL7Exception(
                            "Invalid concept ID '" + valueIdentifier + "' for OBX-5 value '" + valueName + "'");
                }
            }
        } else if ("DT".equals(hl7Datatype)) {
            DT value = (DT) obx5;
            Date valueDate = getDate(value.getYear(), value.getMonth(), value.getDay(), 0, 0, 0);
            if (value == null || valueDate == null) {
                log.warn("Not creating null valued obs for concept " + concept);
                return null;
            }
            obs.setValueDatetime(valueDate);
        } else if ("TS".equals(hl7Datatype)) {
            DTM value = ((TS) obx5).getTime();
            Date valueDate = getDate(value.getYear(), value.getMonth(), value.getDay(), value.getHour(),
                    value.getMinute(), value.getSecond());
            if (value == null || valueDate == null) {
                log.warn("Not creating null valued obs for concept " + concept);
                return null;
            }
            obs.setValueDatetime(valueDate);
        } else if ("TM".equals(hl7Datatype)) {
            TM value = (TM) obx5;
            Date valueTime = getDate(0, 0, 0, value.getHour(), value.getMinute(), value.getSecond());
            if (value == null || valueTime == null) {
                log.warn("Not creating null valued obs for concept " + concept);
                return null;
            }
            obs.setValueDatetime(valueTime);
        } else if ("ST".equals(hl7Datatype)) {
            ST value = (ST) obx5;
            if (value == null || value.getValue() == null || value.getValue().trim().length() == 0) {
                log.warn("Not creating null valued obs for concept " + concept);
                return null;
            }
            obs.setValueText(value.getValue());
        } else {
            // unsupported data type
            // TODO: support RP (report), SN (structured numeric)
            // do we need to support BIT just in case it slips thru?
            throw new HL7Exception("Unsupported observation datatype '" + hl7Datatype + "'");
        }

        return obs;
    }

    private Date getDatetime(OBX obx) throws HL7Exception {
        TS ts = obx.getDateTimeOfTheObservation();
        return getDatetime(ts);
    }

    private Date getDatetime(TS ts) throws HL7Exception {
        Date datetime = null;
        DTM value = ts.getTime();

        if (value.getYear() == 0 || value.getValue() == null)
            return null;

        try {
            datetime = getDate(value.getYear(), value.getMonth(), value.getDay(), value.getHour(),
                    value.getMinute(), value.getSecond());
        } catch (DataTypeException e) {

        }
        return datetime;

    }

    private Date getDate(int year, int month, int day, int hour, int minute, int second) {
        Calendar cal = Calendar.getInstance();
        // Calendar.set(MONTH, int) is zero-based, Hl7 is not
        cal.set(year, month - 1, day, hour, minute, second);
        return cal.getTime();
    }

    private Date getEncounterDate() throws HL7Exception {
        return new Date();
    }

    private Location getLocation(MSH msh) throws HL7Exception {
        String hl7Location = msh.getSendingFacility().getNamespaceID().toString();
        Location location = null;

        List<Location> locationsList = Context.getLocationService().getAllLocations();
        for (Location l : locationsList) {
            String fosaid = l.getDescription();
            String elid = null;

            if (fosaid != null) {
                final Matcher matcher = Pattern.compile(":").matcher(fosaid);
                if (matcher.find()) {
                    elid = fosaid.substring(matcher.end()).trim();
                    if (elid.equals(hl7Location)) {
                        location = l;
                    }
                }
            }
        }

        return location;
    }

    /**
     * Derive a concept name from the CWE component of an hl7 message.
     * 
     * @param cwe
     * @return
     * @throws HL7Exception
     */
    private ConceptName getConceptName(CWE cwe) throws HL7Exception {
        ST altIdentifier = cwe.getAlternateIdentifier();
        ID altCodingSystem = cwe.getNameOfAlternateCodingSystem();
        return getConceptName(altIdentifier, altCodingSystem);
    }

    /**
     * Derive a concept name from the CE component of an hl7 message.
     * 
     * @param ce
     * @return
     * @throws HL7Exception
     */
    private ConceptName getConceptName(CE ce) throws HL7Exception {
        ST altIdentifier = ce.getIdentifier();
        ID altCodingSystem = ce.getNameOfAlternateCodingSystem();
        return getConceptName(altIdentifier, altCodingSystem);
    }

    private ConceptName getConceptName(ST altIdentifier, ID altCodingSystem) throws HL7Exception {
        if (altIdentifier != null) {
            if (HL7Constants.HL7_LOCAL_CONCEPT_NAME.equals(altCodingSystem.getValue())) {
                String hl7ConceptNameId = altIdentifier.getValue();
                return getConceptName(hl7ConceptNameId);
            } else {
                String hl7ConceptNameId = altIdentifier.getValue();
                return getConceptName(hl7ConceptNameId);
            }
        }

        return null;
    }

    private ConceptName getConceptName(String hl7ConceptNameId) throws HL7Exception {
        ConceptName specifiedConceptName = null;
        if (hl7ConceptNameId != null) {
            // get the exact concept name specified by the id
            try {
                Integer conceptNameId = new Integer(hl7ConceptNameId);
                specifiedConceptName = new ConceptName();
                specifiedConceptName.setConceptNameId(conceptNameId);
            } catch (NumberFormatException e) {
                // if it is not a valid number, more than likely it is a bad hl7 message
                log.debug("Invalid concept name ID '" + hl7ConceptNameId + "'", e);
            }
        }
        return specifiedConceptName;

    }

    private Concept getConcept(CE codedElement, String uid) throws HL7Exception {
        String hl7ConceptId = codedElement.getIdentifier().getValue();

        String codingSystem = codedElement.getNameOfCodingSystem().getValue().toString();
        Concept concept = Context.getConceptService().getConceptByMapping(hl7ConceptId, codingSystem);

        return concept;
    }

    private boolean isConceptProposal(String identifier) {
        return OpenmrsUtil.nullSafeEquals(identifier, OpenmrsConstants.PROPOSED_CONCEPT_IDENTIFIER);
    }

    private Person getProvider(PV1 pv1) throws HL7Exception {
        String providerId = pv1.getAttendingDoctor(0).getIDNumber().getValue();

        List<PatientIdentifierType> identifierTypeList = null;
        Person provider = null;

        if (providerId != null) {

            Person person = service.getPersonByEPID(providerId);

            if (person != null)
                provider = person;
            else {

                log.info(
                        "ID extracted from the HL7 message does not match with SHR records, a new provider will be created...");

                Person candidate = new Person();
                candidate.addName(new PersonName(pv1.getAttendingDoctor(0).getGivenName().getValue(), "",
                        pv1.getAttendingDoctor(0).getFamilyName().getSurname().getValue()));
                PersonAttributeType epidType = Context.getPersonService().getPersonAttributeTypeByName("EPID");

                if (epidType == null) {
                    log.info("Creating a PersonAttributeType for EPID since it does not exsist");
                    epidType = new PersonAttributeType();
                    epidType.setName("EPID");
                    epidType.setDescription("Stores the EPID of the Provider");
                    Context.getPersonService().savePersonAttributeType(epidType);
                }

                PersonAttributeType roleType = Context.getPersonService().getPersonAttributeTypeByName("Role");

                if (roleType == null) {
                    log.info("Creating a PersonAttributeType for Role since it does not exsist");
                    roleType = new PersonAttributeType();
                    roleType.setName("Role");
                    roleType.setDescription("Stores the Role of the Person object");
                    Context.getPersonService().savePersonAttributeType(roleType);
                }

                candidate.addAttribute(new PersonAttribute(epidType, providerId));
                candidate.addAttribute(new PersonAttribute(roleType, "Provider"));

                candidate.setGender("N/A");
                candidate = Context.getPersonService().savePerson(candidate);
                provider = candidate;
            }
        }
        return provider;
    }

    private EncounterType getEncounterType(PV1 pv1) throws HL7Exception {
        String encountertypeName = pv1.getAdmissionType().getValue().toString();
        if (encountertypeName.equals("BIR")) {
            encountertypeName = "RapidSMS Notification BIRTH";
        } else if (encountertypeName.equals("RISK")) {
            encountertypeName = "RapidSMS Notification RISK";
        } else if (encountertypeName.equals("MAT")) {
            encountertypeName = "RapidSMS Notification Maternal Death";
        }
        EncounterType encType = Context.getEncounterService().getEncounterType(encountertypeName);
        if (encType == null) {
            encType = new EncounterType(encountertypeName,
                    encountertypeName + " Encounter type created by Rsms notification");
            Context.getEncounterService().saveEncounterType(encType);
        }
        return encType;
    }

    private Encounter createEncounter(Patient patient) {
        Encounter encounter = new Encounter();
        encounter.setPatient(patient);

        return encounter;
    }

    private Patient getPatient(PID pid) throws HL7Exception {
        Patient patient;
        PatientIdentifierType patientIdentifierType = Context.getPatientService()
                .getPatientIdentifierTypeByName("ECID");
        List<PatientIdentifierType> identifierTypeList = new ArrayList<PatientIdentifierType>();
        identifierTypeList.add(patientIdentifierType);

        List<Patient> patients = Context.getPatientService().getPatients(null, enterpriseId, identifierTypeList,
                false);
        //I am not checking the identifier type here. Need to come back and add a check for this
        if (patients.size() == 1) {
            patient = patients.get(0);
        } else {
            throw new HL7Exception("Could not resolve patient");
        }
        return patient;
    }

    private MSH getMSH(ORU_R01 oru) {
        return oru.getMSH();
    }

    private PV1 getPV1(ORU_R01 oru) {
        return oru.getPATIENT_RESULT().getPATIENT().getVISIT().getPV1();
    }

    public boolean canProcess(Message message) {
        return message != null && "ORU_R01".equals(message.getName());
    }

    private PID getPID(ORU_R01 oru) {
        return oru.getPATIENT_RESULT().getPATIENT().getPID();
    }
}