/**
* 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.hl7.impl;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.Encounter;
import org.openmrs.Location;
import org.openmrs.Patient;
import org.openmrs.PatientIdentifier;
import org.openmrs.PatientIdentifierType;
import org.openmrs.Person;
import org.openmrs.PersonName;
import org.openmrs.User;
import org.openmrs.api.APIException;
import org.openmrs.api.PatientIdentifierException;
import org.openmrs.api.context.Context;
import org.openmrs.api.context.UserContext;
import org.openmrs.api.impl.BaseOpenmrsService;
import org.openmrs.hl7.HL7Constants;
import org.openmrs.hl7.HL7InArchive;
import org.openmrs.hl7.HL7InError;
import org.openmrs.hl7.HL7InQueue;
import org.openmrs.hl7.HL7Service;
import org.openmrs.hl7.HL7Source;
import org.openmrs.hl7.HL7Util;
import org.openmrs.hl7.Hl7InArchivesMigrateThread;
import org.openmrs.hl7.Hl7InArchivesMigrateThread.Status;
import org.openmrs.hl7.db.HL7DAO;
import org.openmrs.util.FormConstants;
import org.openmrs.util.OpenmrsConstants;
import org.openmrs.util.OpenmrsUtil;
import org.openmrs.validator.PatientIdentifierValidator;
import ca.uhn.hl7v2.HL7Exception;
import ca.uhn.hl7v2.app.Application;
import ca.uhn.hl7v2.app.ApplicationException;
import ca.uhn.hl7v2.app.MessageTypeRouter;
import ca.uhn.hl7v2.model.Message;
import ca.uhn.hl7v2.model.v25.datatype.CX;
import ca.uhn.hl7v2.model.v25.datatype.ID;
import ca.uhn.hl7v2.model.v25.datatype.PL;
import ca.uhn.hl7v2.model.v25.datatype.TS;
import ca.uhn.hl7v2.model.v25.datatype.XCN;
import ca.uhn.hl7v2.model.v25.datatype.XPN;
import ca.uhn.hl7v2.model.v25.segment.NK1;
import ca.uhn.hl7v2.model.v25.segment.PID;
import ca.uhn.hl7v2.parser.EncodingNotSupportedException;
import ca.uhn.hl7v2.parser.GenericParser;
/**
* OpenMRS HL7 API default methods This class shouldn't be instantiated by itself. Use the
* {@link org.openmrs.api.context.Context}
*
* @see org.openmrs.hl7.HL7Service
*/
public class HL7ServiceImpl extends BaseOpenmrsService implements HL7Service {
private final Log log = LogFactory.getLog(this.getClass());
private static HL7ServiceImpl instance;
protected HL7DAO dao;
private GenericParser parser;
private MessageTypeRouter router;
/**
* Private constructor to only support on singleton instance.
*
* @see #getInstance()
*/
private HL7ServiceImpl() {
}
/**
* Singleton Factory method
*
* @return a singleton instance of this HL7ServiceImpl class
*/
public static HL7ServiceImpl getInstance() {
if (instance == null) {
instance = new HL7ServiceImpl();
}
return instance;
}
/**
* @see org.openmrs.hl7.HL7Service#setHL7DAO(org.openmrs.hl7.db.HL7DAO)
*/
public void setHL7DAO(HL7DAO dao) {
this.dao = dao;
}
/**
* Used by spring to inject the parser
*
* @param parser the parser to use
*/
public void setParser(GenericParser parser) {
this.parser = parser;
}
/**
* Used by spring to inject the router
*
* @param router the router to use
*/
public void setRouter(MessageTypeRouter router) {
this.router = router;
}
/**
* @see org.openmrs.hl7.HL7Service#saveHL7Source(org.openmrs.hl7.HL7Source)
*/
public HL7Source saveHL7Source(HL7Source hl7Source) throws APIException {
if (hl7Source.getCreator() == null)
hl7Source.setCreator(Context.getAuthenticatedUser());
if (hl7Source.getDateCreated() == null)
hl7Source.setDateCreated(new Date());
return dao.saveHL7Source(hl7Source);
}
/**
* @see org.openmrs.hl7.HL7Service#purgeHL7Source(org.openmrs.hl7.HL7Source)
*/
public void purgeHL7Source(HL7Source hl7Source) throws APIException {
dao.deleteHL7Source(hl7Source);
}
/**
* @see org.openmrs.hl7.HL7Service#retireHL7Source(org.openmrs.hl7.HL7Source)
*/
public HL7Source retireHL7Source(HL7Source hl7Source) throws APIException {
throw new APIException("Not implemented yet");
}
/**
* @see org.openmrs.hl7.HL7Service#createHL7Source(org.openmrs.hl7.HL7Source)
* @deprecated
*/
@Deprecated
public void createHL7Source(HL7Source hl7Source) {
Context.getHL7Service().saveHL7Source(hl7Source);
}
/**
* @see org.openmrs.hl7.HL7Service#getHL7Source(java.lang.Integer)
*/
public HL7Source getHL7Source(Integer hl7SourceId) {
return dao.getHL7Source(hl7SourceId);
}
/**
* @see org.openmrs.hl7.HL7Service#getAllHL7Sources()
*/
public List<HL7Source> getAllHL7Sources() throws APIException {
return dao.getAllHL7Sources();
}
/**
* @see org.openmrs.hl7.HL7Service#getHL7SourceByName(java.lang.String)
*/
public HL7Source getHL7SourceByName(String name) throws APIException {
return dao.getHL7SourceByName(name);
}
/**
* @see org.openmrs.hl7.HL7Service#getHL7Source(java.lang.String)
* @deprecated
*/
@Deprecated
public HL7Source getHL7Source(String name) {
return getHL7SourceByName(name);
}
/**
* @see org.openmrs.hl7.HL7Service#getHL7Sources()
* @deprecated
*/
@Deprecated
public Collection<HL7Source> getHL7Sources() {
return getAllHL7Sources();
}
/**
* @see org.openmrs.hl7.HL7Service#updateHL7Source(org.openmrs.hl7.HL7Source)
* @deprecated
*/
@Deprecated
public void updateHL7Source(HL7Source hl7Source) {
Context.getHL7Service().saveHL7Source(hl7Source);
}
/**
* @see org.openmrs.hl7.HL7Service#deleteHL7Source(org.openmrs.hl7.HL7Source)
* @deprecated
*/
@Deprecated
public void deleteHL7Source(HL7Source hl7Source) {
Context.getHL7Service().purgeHL7Source(hl7Source);
}
/**
* @see org.openmrs.hl7.HL7Service#getAllHL7InQueues()
*/
public List<HL7InQueue> getAllHL7InQueues() throws APIException {
return dao.getAllHL7InQueues();
}
/**
* @see org.openmrs.hl7.HL7Service#purgeHL7InQueue(org.openmrs.hl7.HL7InQueue)
*/
public void purgeHL7InQueue(HL7InQueue hl7InQueue) {
dao.deleteHL7InQueue(hl7InQueue);
}
/**
* @see org.openmrs.hl7.HL7Service#saveHL7InQueue(org.openmrs.hl7.HL7InQueue)
*/
public HL7InQueue saveHL7InQueue(HL7InQueue hl7InQueue) throws APIException {
if (hl7InQueue.getDateCreated() == null)
hl7InQueue.setDateCreated(new Date());
if (hl7InQueue.getMessageState() == null)
hl7InQueue.setMessageState(HL7Constants.HL7_STATUS_PENDING);
return dao.saveHL7InQueue(hl7InQueue);
}
/**
* @see org.openmrs.hl7.HL7Service#createHL7InQueue(org.openmrs.hl7.HL7InQueue)
* @deprecated
*/
@Deprecated
public void createHL7InQueue(HL7InQueue hl7InQueue) {
Context.getHL7Service().saveHL7InQueue(hl7InQueue);
}
/**
* @see org.openmrs.hl7.HL7Service#getHL7InQueue(java.lang.Integer)
*/
public HL7InQueue getHL7InQueue(Integer hl7InQueueId) {
return dao.getHL7InQueue(hl7InQueueId);
}
/**
* @see org.openmrs.hl7.HL7Service#getHL7InQueues()
* @deprecated
*/
@Deprecated
public Collection<HL7InQueue> getHL7InQueues() {
return getAllHL7InQueues();
}
/**
* @see org.openmrs.hl7.HL7Service#getNextHL7InQueue()
*/
public HL7InQueue getNextHL7InQueue() {
return dao.getNextHL7InQueue();
}
/**
* @see org.openmrs.hl7.HL7Service#deleteHL7InQueue(org.openmrs.hl7.HL7InQueue)
* @deprecated
*/
@Deprecated
public void deleteHL7InQueue(HL7InQueue hl7InQueue) {
Context.getHL7Service().purgeHL7InQueue(hl7InQueue);
}
/**
* @see org.openmrs.hl7.HL7Service#getHL7InArchiveByState(java.lang.Integer)
*/
public List<HL7InArchive> getHL7InArchiveByState(Integer state) throws APIException {
if (!isArchiveMigrationRequired()) {
//if the state is 'processed' return all archives from the file system since their status is processed
if (state.equals(HL7Constants.HL7_STATUS_PROCESSED))
return getAllHL7InArchives();
return Collections.emptyList();
}
throw new APIException("Can't fetch hl7 archives by state before hl7 in archive migration has been run");
}
/**
* @see org.openmrs.hl7.HL7Service#getHL7InQueueByState(java.lang.Integer)
*/
public List<HL7InQueue> getHL7InQueueByState(Integer state) throws APIException {
return dao.getHL7InQueueByState(state);
}
/**
* @see org.openmrs.hl7.HL7Service#getAllHL7InArchives()
*/
public List<HL7InArchive> getAllHL7InArchives() throws APIException {
if (!isArchiveMigrationRequired())
return dao.getAllHL7InArchivesInFileSystem();
throw new APIException("Can't fetch all hl7 archives before hl7 in archive migration has been run");
}
/**
* @see org.openmrs.hl7.HL7Service#purgeHL7InArchive(org.openmrs.hl7.HL7InArchive)
*/
public void purgeHL7InArchive(HL7InArchive hl7InArchive) throws APIException {
if (hl7InArchive != null) {
if (!isArchiveMigrationRequired())
dao.deleteHL7InArchiveInFileSystem(hl7InArchive.getUuid());
else
throw new APIException("Can't purge hl7 archive before hl7 in archive migration has been run and completed");
}
}
/**
* @see org.openmrs.hl7.HL7Service#saveHL7InArchive(org.openmrs.hl7.HL7InArchive)
*/
public HL7InArchive saveHL7InArchive(HL7InArchive hl7InArchive) throws APIException {
hl7InArchive.setDateCreated(new Date());
return dao.saveHL7InArchiveToFileSystem(hl7InArchive);
}
/**
* @see org.openmrs.hl7.HL7Service#createHL7InArchive(org.openmrs.hl7.HL7InArchive)
* @deprecated
*/
@Deprecated
public void createHL7InArchive(HL7InArchive hl7InArchive) {
Context.getHL7Service().saveHL7InArchive(hl7InArchive);
}
/**
* @see org.openmrs.hl7.HL7Service#getHL7InArchive(java.lang.Integer)
*/
public HL7InArchive getHL7InArchive(Integer hl7InArchiveId) {
if (!isArchiveMigrationRequired())
throw new APIException(
"The method 'getHL7InArchive(Integer hl7InArchiveId)' should not be called after"
+ " migration of archives has been done, instead use getHl7InArchiveByUuid()");
//migration is running
else if (isArchiveMigrationRequired() && Hl7InArchivesMigrateThread.getTransferStatus() != Status.NONE)
throw new APIException("Can't retrieve hl7 archive by id while archive migration is running");
return dao.getHL7InArchive(hl7InArchiveId);
}
/**
* @see org.openmrs.hl7.HL7Service#getHL7InArchives()
* @deprecated
*/
@Deprecated
public Collection<HL7InArchive> getHL7InArchives() {
return getAllHL7InArchives();
}
/**
* @see org.openmrs.hl7.HL7Service#updateHL7InArchive(org.openmrs.hl7.HL7InArchive)
* @deprecated
*/
@Deprecated
public void updateHL7InArchive(HL7InArchive hl7InArchive) {
Context.getHL7Service().saveHL7InArchive(hl7InArchive);
}
/**
* @see org.openmrs.hl7.HL7Service#deleteHL7InArchive(org.openmrs.hl7.HL7InArchive)
* @deprecated
*/
@Deprecated
public void deleteHL7InArchive(HL7InArchive hl7InArchive) {
Context.getHL7Service().purgeHL7InArchive(hl7InArchive);
}
/**
* @see org.openmrs.hl7.HL7Service#getAllHL7InErrors()
*/
public List<HL7InError> getAllHL7InErrors() throws APIException {
return dao.getAllHL7InErrors();
}
/**
* @see org.openmrs.hl7.HL7Service#purgeHL7InError(org.openmrs.hl7.HL7InError)
*/
public void purgeHL7InError(HL7InError hl7InError) throws APIException {
dao.deleteHL7InError(hl7InError);
}
/**
* @see org.openmrs.hl7.HL7Service#saveHL7InError(org.openmrs.hl7.HL7InError)
*/
public HL7InError saveHL7InError(HL7InError hl7InError) throws APIException {
hl7InError.setDateCreated(new Date());
return dao.saveHL7InError(hl7InError);
}
/**
* @see org.openmrs.hl7.HL7Service#createHL7InError(org.openmrs.hl7.HL7InError)
* @deprecated
*/
@Deprecated
public void createHL7InError(HL7InError hl7InError) {
Context.getHL7Service().saveHL7InError(hl7InError);
}
/**
* @see org.openmrs.hl7.HL7Service#getHL7InError(java.lang.Integer)
*/
public HL7InError getHL7InError(Integer hl7InErrorId) {
return dao.getHL7InError(hl7InErrorId);
}
/**
* @see org.openmrs.hl7.HL7Service#getHL7InErrors()
* @deprecated
*/
@Deprecated
public Collection<HL7InError> getHL7InErrors() {
return dao.getAllHL7InErrors();
}
/**
* @deprecated
* @see org.openmrs.hl7.HL7Service#updateHL7InError(org.openmrs.hl7.HL7InError)
*/
@Deprecated
public void updateHL7InError(HL7InError hl7InError) {
Context.getHL7Service().saveHL7InError(hl7InError);
}
/**
* @see org.openmrs.hl7.HL7Service#deleteHL7InError(org.openmrs.hl7.HL7InError)
* @deprecated
*/
@Deprecated
public void deleteHL7InError(HL7InError hl7InError) {
Context.getHL7Service().purgeHL7InError(hl7InError);
}
/**
* @param xcn HL7 component of data type XCN (extended composite ID number and name for persons)
* (see HL7 2.5 manual Ch.2A.86)
* @return Internal ID # of the specified user, or null if that user can't be found or is
* ambiguous
*/
public Integer resolveUserId(XCN xcn) throws HL7Exception {
// TODO: properly handle family and given names. For now I'm treating
// givenName+familyName as a username.
String idNumber = xcn.getIDNumber().getValue();
String familyName = xcn.getFamilyName().getSurname().getValue();
String givenName = xcn.getGivenName().getValue();
// unused
// String assigningAuthority = xcn.getAssigningAuthority()
// .getUniversalID().getValue();
/*
* if ("null".equals(familyName)) familyName = null; if
* ("null".equals(givenName)) givenName = null; if
* ("null".equals(assigningAuthority)) assigningAuthority = null;
*/
if (idNumber != null && idNumber.length() > 0) {
// log.debug("searching for user by id " + idNumber);
try {
Integer userId = new Integer(idNumber);
User user = Context.getUserService().getUser(userId);
return user.getUserId();
}
catch (Exception e) {
log.error("Invalid user ID '" + idNumber + "'", e);
return null;
}
} else {
// log.debug("searching for user by name");
try {
StringBuilder username = new StringBuilder();
if (familyName != null) {
username.append(familyName);
}
if (givenName != null) {
if (username.length() > 0)
username.append(" "); // separate names with a space
username.append(givenName);
}
// log.debug("looking for username '" + username + "'");
User user = Context.getUserService().getUserByUsername(username.toString());
return user.getUserId();
}
catch (Exception e) {
log.error("Error resolving user with id '" + idNumber + "' family name '" + familyName
+ "' and given name '" + givenName + "'", e);
return null;
}
}
}
/**
* @see org.openmrs.hl7.HL7Service#resolvePersonId(ca.uhn.hl7v2.model.v25.datatype.XCN)
*/
public Integer resolvePersonId(XCN xcn) throws HL7Exception {
String idNumber = xcn.getIDNumber().getValue();
String familyName = xcn.getFamilyName().getSurname().getValue();
String givenName = xcn.getGivenName().getValue();
if (idNumber != null && idNumber.length() > 0) {
try {
Person person = Context.getPersonService().getPerson(new Integer(idNumber));
return person.getPersonId();
}
catch (Exception e) {
log.error("Invalid person ID '" + idNumber + "'", e);
return null;
}
} else {
List<Person> persons = Context.getPersonService().getPeople(givenName + " " + familyName, null);
if (persons.size() == 1) {
return persons.get(0).getPersonId();
} else if (persons.size() == 0) {
log.error("Couldn't find a person named " + givenName + " " + familyName);
return null;
} else {
log.error("Found more than one person named " + givenName + " " + familyName);
return null;
}
}
}
/**
* @param pl HL7 component of data type PL (person location) (see Ch 2.A.53)
* @return internal identifier of the specified location, or null if it is not found or
* ambiguous
*/
public Integer resolveLocationId(PL pl) throws HL7Exception {
// TODO: Get rid of hack that allows first component to be an integer
// location.location_id
String pointOfCare = pl.getPointOfCare().getValue();
String facility = pl.getFacility().getUniversalID().getValue();
// HACK: try to treat the first component (which should be "Point of
// Care" as an internal openmrs location_id
try {
Integer locationId = new Integer(pointOfCare);
Location l = Context.getLocationService().getLocation(locationId);
return l == null ? null : l.getLocationId();
}
catch (Exception ex) {
if (facility == null) { // we have no tricks left up our sleeve, so
// throw an exception
throw new HL7Exception("Error trying to treat PL.pointOfCare '" + pointOfCare
+ "' as a location.location_id", ex);
}
}
// Treat the 4th component "Facility" as location.name
try {
Location l = Context.getLocationService().getLocation(facility);
if (l == null) {
log.debug("Couldn't find a location named '" + facility + "'");
}
return l == null ? null : l.getLocationId();
}
catch (Exception ex) {
log.error("Error trying to treat PL.facility '" + facility + "' as a location.name", ex);
return null;
}
}
/**
* @param pid A PID segment of an hl7 message
* @return The internal id number of the Patient described by the PID segment, or null of the
* patient is not found, or if the PID segment is ambiguous
* @throws HL7Exception
*/
public Integer resolvePatientId(PID pid) throws HL7Exception {
Person p = resolvePersonFromIdentifiers(pid.getPatientIdentifierList());
if (p != null && p.isPatient())
return p.getPersonId();
return null;
}
/**
* @param identifiers CX identifier list from an identifier (either PID or NK1)
* @return The internal id number of the Patient based on one of the given identifiers, or null
* if the patient is not found
* @throws HL7Exception
*/
public Person resolvePersonFromIdentifiers(CX[] identifiers) throws HL7Exception {
// TODO: Properly handle assigning authority. If specified it's
// currently treated as PatientIdentifierType.name
// TODO: Throw exceptions instead of returning null in some cases
// give up if no identifiers exist
if (identifiers.length < 1)
throw new HL7Exception("Missing patient identifier in PID segment");
// TODO other potential identifying characteristics in PID we could use
// to identify the patient
// XPN[] patientName = pid.getPersonName();
// String gender = pid.getAdministrativeSex().getValue();
// TS dateOfBirth = pid.getDateTimeOfBirth();
// Take the first uniquely matching identifier
for (CX identifier : identifiers) {
String hl7PersonId = identifier.getIDNumber().getValue();
// TODO if 1st component is blank, check 2nd and 3rd of assigning authority
String assigningAuthority = identifier.getAssigningAuthority().getNamespaceID().getValue();
if (StringUtils.isNotBlank(assigningAuthority)) {
// Assigning authority defined
try {
PatientIdentifierType pit = Context.getPatientService().getPatientIdentifierTypeByName(
assigningAuthority);
if (pit == null) {
// there is no matching PatientIdentifierType
if (assigningAuthority.equals(FormConstants.HL7_AUTHORITY_UUID)) {
// the identifier is a UUID
Person p = Context.getPersonService().getPersonByUuid(hl7PersonId);
if (p != null)
return p;
log.warn("Can't find person for UUID '" + hl7PersonId + "'");
continue; // skip identifiers with unknown type
} else if (assigningAuthority.equals(FormConstants.HL7_AUTHORITY_LOCAL)) {
// the ID is internal (local)
String idType = identifier.getIdentifierTypeCode().getValue();
try {
if (idType.equals(FormConstants.HL7_ID_PERSON)) {
Integer pid = Integer.parseInt(hl7PersonId);
// patient_id == person_id, so just look for the person
Person p = Context.getPersonService().getPerson(pid);
if (p != null)
return p;
} else if (idType.equals(FormConstants.HL7_ID_PATIENT)) {
Integer pid = Integer.parseInt(hl7PersonId);
// patient_id == person_id, so just look for the person
Patient p = Context.getPatientService().getPatient(pid);
if (p != null)
return p;
}
}
catch (NumberFormatException e) {}
log.warn("Can't find Local identifier of '" + hl7PersonId + "'");
continue; // skip identifiers with unknown type
}
log.warn("Can't find PatientIdentifierType named '" + assigningAuthority + "'");
continue; // skip identifiers with unknown type
}
List<PatientIdentifier> matchingIds = Context.getPatientService().getPatientIdentifiers(hl7PersonId,
Collections.singletonList(pit), null, null, null);
if (matchingIds == null || matchingIds.size() < 1) {
// no matches
log.warn("NO matches found for " + hl7PersonId);
continue; // try next identifier
} else if (matchingIds.size() == 1) {
// unique match -- we're done
return matchingIds.get(0).getPatient();
} else {
// ambiguous identifier
log.debug("Ambiguous identifier in PID. " + matchingIds.size() + " matches for identifier '"
+ hl7PersonId + "' of type '" + pit + "'");
continue; // try next identifier
}
}
catch (Exception e) {
log.error("Error resolving patient identifier '" + hl7PersonId + "' for assigning authority '"
+ assigningAuthority + "'", e);
continue;
}
} else {
try {
log.debug("CX contains ID '" + hl7PersonId
+ "' without assigning authority -- assuming patient.patient_id");
return Context.getPatientService().getPatient(Integer.parseInt(hl7PersonId));
}
catch (NumberFormatException e) {
log.warn("Invalid patient ID '" + hl7PersonId + "'");
}
}
}
return null;
}
/**
* @see org.openmrs.hl7.HL7Service#garbageCollect()
*/
public void garbageCollect() {
dao.garbageCollect();
}
/**
* @see org.openmrs.hl7.HL7Service#encounterCreated(org.openmrs.Encounter)
* @deprecated This method is no longer needed. When an encounter is created in the ROUR01
* handler, it is created with all obs. Any AOP hooking should be done on the
* EncounterService.createEncounter(Encounter) method
*/
@Deprecated
public void encounterCreated(Encounter encounter) {
// nothing is done here in core. Modules override/hook on this method
}
/**
* @see org.openmrs.hl7.HL7Service#processHL7InQueue(org.openmrs.hl7.HL7InQueue)
*/
public HL7InQueue processHL7InQueue(HL7InQueue hl7InQueue) throws HL7Exception {
if (hl7InQueue == null)
throw new HL7Exception("hl7InQueue argument cannot be null");
// mark this queue object as processing so that it isn't processed twice
if (OpenmrsUtil.nullSafeEquals(HL7Constants.HL7_STATUS_PROCESSING, hl7InQueue.getMessageState()))
throw new HL7Exception("The hl7InQueue message with id: " + hl7InQueue.getHL7InQueueId()
+ " is already processing. " + ",key=" + hl7InQueue.getHL7SourceKey() + ")");
else
hl7InQueue.setMessageState(HL7Constants.HL7_STATUS_PROCESSING);
if (log.isDebugEnabled())
log.debug("Processing HL7 inbound queue (id=" + hl7InQueue.getHL7InQueueId() + ",key="
+ hl7InQueue.getHL7SourceKey() + ")");
// Parse the HL7 into an HL7Message or abort with failure
String hl7Message = hl7InQueue.getHL7Data();
try {
// Parse the inbound HL7 message using the parser
// NOT making a direct call here so that AOP can happen around this method
Message parsedMessage = Context.getHL7Service().parseHL7String(hl7Message);
// Send the parsed message to our receiver routine for processing into db
// NOT making a direct call here so that AOP can happen around this method
Message result = Context.getHL7Service().processHL7Message(parsedMessage);
// Move HL7 inbound queue entry into the archive before exiting
log.debug("Archiving HL7 inbound queue entry");
Context.getHL7Service().saveHL7InArchive(new HL7InArchive(hl7InQueue));
log.debug("Removing HL7 message from inbound queue");
Context.getHL7Service().purgeHL7InQueue(hl7InQueue);
}
catch (HL7Exception e) {
boolean skipError = false;
log.debug("Unable to process hl7inqueue: " + hl7InQueue.getHL7InQueueId(), e);
log.debug("Hl7inqueue source: " + hl7InQueue.getHL7Source());
log.debug("hl7_processor.ignore_missing_patient_non_local? "
+ Context.getAdministrationService().getGlobalProperty(
OpenmrsConstants.GLOBAL_PROPERTY_IGNORE_MISSING_NONLOCAL_PATIENTS, "false"));
if (e.getCause() != null
&& e.getCause().getMessage().equals("Could not resolve patient")
&& !hl7InQueue.getHL7Source().getName().equals("local")
&& Context.getAdministrationService().getGlobalProperty(
OpenmrsConstants.GLOBAL_PROPERTY_IGNORE_MISSING_NONLOCAL_PATIENTS, "false").equals("true")) {
skipError = true;
}
if (!skipError)
setFatalError(hl7InQueue, "Trouble parsing HL7 message (" + hl7InQueue.getHL7SourceKey() + ")", e);
}
catch (Throwable t) {
setFatalError(hl7InQueue, "Exception while attempting to process HL7 In Queue (" + hl7InQueue.getHL7SourceKey()
+ ")", t);
}
return hl7InQueue;
}
/**
* Convenience method to respond to fatal errors by moving the queue entry into an error bin
* prior to aborting
*/
private void setFatalError(HL7InQueue hl7InQueue, String error, Throwable cause) {
HL7InError hl7InError = new HL7InError(hl7InQueue);
hl7InError.setError(error);
if (cause == null)
hl7InError.setErrorDetails("");
else {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw, true);
cause.printStackTrace(pw);
pw.flush();
sw.flush();
hl7InError.setErrorDetails(sw.toString());
}
Context.getHL7Service().saveHL7InError(hl7InError);
Context.getHL7Service().purgeHL7InQueue(hl7InQueue);
log.error(error, cause);
}
/**
* @see org.openmrs.hl7.HL7Service#parseHL7Message(java.lang.String)
*/
public Message parseHL7String(String hl7Message) throws HL7Exception {
// Any pre-parsing for HL7 messages would go here
// or a module can use AOP to pre-parse the message
// First, try and parse the message
Message message;
try {
message = parser.parse(hl7Message);
}
catch (EncodingNotSupportedException e) {
throw new HL7Exception("HL7 encoding not supported", e);
}
catch (ca.uhn.hl7v2.HL7Exception e) {
throw new HL7Exception("Error parsing message", e);
}
return message;
}
/**
* @see org.openmrs.hl7.HL7Service#startHl7ArchiveMigration(UserContext)
*/
public boolean startHl7ArchiveMigration() throws APIException {
if (Hl7InArchivesMigrateThread.getTransferStatus() != Status.NONE)
return false;
return Hl7InArchivesMigrateThread.startArchiveMigration();
}
/**
* @see org.openmrs.hl7.HL7Service#stopHl7ArchiveMigration(UserContext)
*/
public void stopHl7ArchiveMigration() throws APIException {
if (Hl7InArchivesMigrateThread.getHl7InArchivesMigrateThread() != null)
Hl7InArchivesMigrateThread.getHl7InArchivesMigrateThread().stopArchiveMigration();
}
/**
* @see org.openmrs.hl7.HL7Service#migrateHl7InArchivesToFileSystem(Map)
*/
@Override
public void migrateHl7InArchivesToFileSystem(Map<String, Integer> progressStatusMap) throws APIException {
dao.migrateHl7InArchivesToFileSystem(progressStatusMap);
}
/**
* @see org.openmrs.hl7.HL7Service#isArchiveMigrationRequired()
*/
@Override
public boolean isArchiveMigrationRequired() throws APIException {
return dao.isArchiveMigrationRequired();
}
/**
* @see org.openmrs.hl7.HL7Service#getHL7InArchiveByUuid(java.lang.String)
*/
@Override
public HL7InArchive getHL7InArchiveByUuid(String uuid) throws APIException {
if (!isArchiveMigrationRequired())
return dao.getHL7InArchiveByUuidFromFileSystem(uuid);
else if (isArchiveMigrationRequired() && Hl7InArchivesMigrateThread.getTransferStatus() == Status.NONE)
return dao.getHL7InArchiveByUuid(uuid);
throw new APIException("Can't retrieve hl7 archive by uuid while archive migration is running");
}
/**
* @see org.openmrs.hl7.HL7Service#processHL7Message(ca.uhn.hl7v2.model.Message)
*/
public Message processHL7Message(Message message) throws HL7Exception {
// Any post-parsing (pre-routing) processing would go here
// or a module can use AOP to do the post-parsing
Message response;
try {
if (!router.canProcess(message))
throw new HL7Exception("No route for hl7 message: " + message.getName()
+ ". Make sure you have a module installed that registers a hl7handler for this type");
response = router.processMessage(message);
}
catch (ApplicationException e) {
throw new HL7Exception("Error while processing HL7 message: " + message.getName(), e);
}
return response;
}
/**
* Sets the given handlers as router applications that are available to HAPI when it is parsing
* an hl7 message.<br/>
* This method is usually used by Spring and the handlers are set in the
* applicationContext-server.xml method.<br/>
* The key in the map is a string like "ORU_R01" where the first part is the message type and
* the second is the trigger event.
*
* @param handlers a map from MessageName to Application object
*/
public void setHL7Handlers(Map<String, Application> handlers) {
// loop over all the given handlers and add them to the router
for (Map.Entry<String, Application> entry : handlers.entrySet()) {
String messageName = entry.getKey();
if (!messageName.contains("_"))
throw new APIException("Invalid messageName. The format must be messageType_triggerEvent, e.g: ORU_R01");
String messageType = messageName.split("_")[0];
String triggerEvent = messageName.split("_")[1];
router.registerApplication(messageType, triggerEvent, entry.getValue());
}
}
/**
* @see org.openmrs.hl7.HL7Service#createPersonFromNK1(ca.uhn.hl7v2.model.v25.segment.NK1)
*/
public Person createPersonFromNK1(NK1 nk1) throws HL7Exception {
// NOTE: following block (with minor modifications) stolen from ADTA28Handler
// TODO: generalize this for use with both PID and NK1 segments
Person person = new Person();
// UUID
CX[] identifiers = nk1.getNextOfKinAssociatedPartySIdentifiers();
String uuid = getUuidFromIdentifiers(identifiers);
if (Context.getPersonService().getPersonByUuid(uuid) != null)
throw new HL7Exception("Non-unique UUID '" + uuid + "' for new person");
person.setUuid(uuid);
// Patient Identifiers
List<PatientIdentifier> goodIdentifiers = new ArrayList<PatientIdentifier>();
for (CX id : identifiers) {
String assigningAuthority = id.getAssigningAuthority().getNamespaceID().getValue();
String hl7PatientId = id.getIDNumber().getValue();
log.debug("identifier has id=" + hl7PatientId + " assigningAuthority=" + assigningAuthority);
if (assigningAuthority != null && assigningAuthority.length() > 0) {
try {
PatientIdentifierType pit = Context.getPatientService().getPatientIdentifierTypeByName(
assigningAuthority);
if (pit == null) {
if (!assigningAuthority.equals("UUID"))
log.warn("Can't find PatientIdentifierType named '" + assigningAuthority + "'");
continue; // skip identifiers with unknown type
}
PatientIdentifier pi = new PatientIdentifier();
pi.setIdentifierType(pit);
pi.setIdentifier(hl7PatientId);
// Get default location
Location location = Context.getLocationService().getDefaultLocation();
if (location == null) {
throw new HL7Exception("Cannot find default location");
}
pi.setLocation(location);
try {
PatientIdentifierValidator.validateIdentifier(pi);
goodIdentifiers.add(pi);
}
catch (PatientIdentifierException ex) {
log.warn("Patient identifier in NK1 is invalid: " + pi, ex);
}
}
catch (Exception e) {
log.error("Uncaught error parsing/creating patient identifier '" + hl7PatientId
+ "' for assigning authority '" + assigningAuthority + "'", e);
}
}
else {
log.debug("NK1 contains identifier with no assigning authority");
continue;
}
}
if (!goodIdentifiers.isEmpty()) {
// cast the person as a Patient and add identifiers
person = new Patient(person);
((Patient) person).addIdentifiers(goodIdentifiers);
}
// Person names
for (XPN patientNameX : nk1.getNKName()) {
PersonName name = new PersonName();
name.setFamilyName(patientNameX.getFamilyName().getSurname().getValue());
name.setGivenName(patientNameX.getGivenName().getValue());
name.setMiddleName(patientNameX.getSecondAndFurtherGivenNamesOrInitialsThereof().getValue());
person.addName(name);
}
// Gender (checks for null, but not for 'M' or 'F')
String gender = nk1.getAdministrativeSex().getValue();
if (gender == null)
throw new HL7Exception("Missing gender in an NK1 segment");
gender = gender.toUpperCase();
if (!OpenmrsConstants.GENDER().containsKey(gender))
throw new HL7Exception("Unrecognized gender: " + gender);
person.setGender(gender);
// Date of Birth
TS dateOfBirth = nk1.getDateTimeOfBirth();
if (dateOfBirth == null || dateOfBirth.getTime() == null || dateOfBirth.getTime().getValue() == null)
throw new HL7Exception("Missing birth date in an NK1 segment");
person.setBirthdate(HL7Util.parseHL7Timestamp(dateOfBirth.getTime().getValue()));
// Estimated birthdate?
ID precisionTemp = dateOfBirth.getDegreeOfPrecision();
if (precisionTemp != null && precisionTemp.getValue() != null) {
String precision = precisionTemp.getValue().toUpperCase();
log.debug("The birthdate is estimated: " + precision);
if (precision.equals("Y") || precision.equals("L"))
person.setBirthdateEstimated(true);
}
// save the new person or patient
if (person instanceof Patient)
Context.getPatientService().savePatient((Patient) person);
else
Context.getPersonService().savePerson(person);
return person;
}
/**
* @see org.openmrs.hl7.HL7Service#getUuidFromIdentifiers(ca.uhn.hl7v2.model.v25.datatype.CX[])
*/
public String getUuidFromIdentifiers(CX[] identifiers) throws HL7Exception {
Boolean found = false;
String uuid = null;
for (CX identifier : identifiers) {
// check for UUID as the assigning authority
if (identifier.getAssigningAuthority().getNamespaceID().getValue().equals("UUID")) {
// check for duplicates
if (found && !identifier.getIDNumber().getValue().equals(uuid))
throw new HL7Exception("multiple UUID values found");
uuid = identifier.getIDNumber().getValue();
found = true;
}
}
// returns null if not found
return uuid;
}
}