Java tutorial
/** * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. * * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS * graphic logo is a trademark of OpenMRS Inc. */ package org.openmrs.hl7.impl; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.net.URI; import java.net.URISyntaxException; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; 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.db.DAOException; 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.HL7QueueItem; 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.OpenmrsConstants; import org.openmrs.util.OpenmrsUtil; import org.openmrs.util.PrivilegeConstants; import org.openmrs.validator.PatientIdentifierValidator; import org.springframework.transaction.annotation.Transactional; 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 */ @Transactional 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("general.not.yet.implemented", (Object[]) null); } /** * @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) */ @Transactional(readOnly = true) public HL7Source getHL7Source(Integer hl7SourceId) { return dao.getHL7Source(hl7SourceId); } /** * @see org.openmrs.hl7.HL7Service#getAllHL7Sources() */ @Transactional(readOnly = true) public List<HL7Source> getAllHL7Sources() throws APIException { return dao.getAllHL7Sources(); } /** * @see org.openmrs.hl7.HL7Service#getHL7SourceByName(java.lang.String) */ @Transactional(readOnly = true) public HL7Source getHL7SourceByName(String name) throws APIException { return dao.getHL7SourceByName(name); } /** * @see org.openmrs.hl7.HL7Service#getHL7Source(java.lang.String) * @deprecated */ @Deprecated @Transactional(readOnly = true) public HL7Source getHL7Source(String name) { return Context.getHL7Service().getHL7SourceByName(name); } /** * @see org.openmrs.hl7.HL7Service#getHL7Sources() * @deprecated */ @Deprecated @Transactional(readOnly = true) public Collection<HL7Source> getHL7Sources() { return Context.getHL7Service().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() */ @Transactional(readOnly = true) public List<HL7InQueue> getAllHL7InQueues() throws APIException { return dao.getAllHL7InQueues(); } /** * @see org.openmrs.hl7.HL7Service#getHL7InQueueBatch(int, int, int, String) */ @Override @Transactional(readOnly = true) public List<HL7InQueue> getHL7InQueueBatch(int start, int length, int messageState, String query) throws APIException { return dao.getHL7Batch(HL7InQueue.class, start, length, messageState, query); } /** * @see org.openmrs.hl7.HL7Service#getHL7InErrorBatch(int, int, java.lang.String) */ @Override @Transactional(readOnly = true) public List<HL7InError> getHL7InErrorBatch(int start, int length, String query) throws APIException { return dao.getHL7Batch(HL7InError.class, start, length, null, query); } /** * @see org.openmrs.hl7.HL7Service#getHL7InArchiveBatch(int, int, int, String) */ @Override @Transactional(readOnly = true) public List<HL7InArchive> getHL7InArchiveBatch(int start, int length, int messageState, String query) throws APIException { return dao.getHL7Batch(HL7InArchive.class, start, length, messageState, query); } /** * @see org.openmrs.hl7.HL7Service#countHL7InQueue(int, java.lang.String) */ @Override @Transactional(readOnly = true) public Integer countHL7InQueue(int messageState, String query) throws APIException { return OpenmrsUtil.convertToInteger(dao.countHL7s(HL7InQueue.class, messageState, query)); } /** * @see org.openmrs.hl7.HL7Service#countHL7InError(java.lang.String) */ @Override @Transactional(readOnly = true) public Integer countHL7InError(String query) throws APIException { return OpenmrsUtil.convertToInteger(dao.countHL7s(HL7InError.class, null, query)); } /** * @see org.openmrs.hl7.HL7Service#countHL7InArchive(int, java.lang.String) */ @Override @Transactional(readOnly = true) public Integer countHL7InArchive(int messageState, String query) throws APIException { return OpenmrsUtil.convertToInteger(dao.countHL7s(HL7InArchive.class, messageState, query)); } /** * @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) */ @Transactional(readOnly = true) public HL7InQueue getHL7InQueue(Integer hl7InQueueId) { return dao.getHL7InQueue(hl7InQueueId); } @Override @Transactional(readOnly = true) public HL7InQueue getHL7InQueueByUuid(String uuid) throws APIException { return dao.getHL7InQueueByUuid(uuid); } /** * @see org.openmrs.hl7.HL7Service#getHL7InQueues() * @deprecated */ @Deprecated @Transactional(readOnly = true) public Collection<HL7InQueue> getHL7InQueues() { return Context.getHL7Service().getAllHL7InQueues(); } /** * @see org.openmrs.hl7.HL7Service#getNextHL7InQueue() */ @Transactional(readOnly = true) 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) */ @Transactional(readOnly = true) public List<HL7InArchive> getHL7InArchiveByState(Integer state) throws APIException { return dao.getHL7InArchiveByState(state); } /** * @see org.openmrs.hl7.HL7Service#getHL7InQueueByState(java.lang.Integer) */ @Transactional(readOnly = true) public List<HL7InQueue> getHL7InQueueByState(Integer state) throws APIException { return dao.getHL7InQueueByState(state); } /** * @see org.openmrs.hl7.HL7Service#getAllHL7InArchives() */ @Transactional(readOnly = true) public List<HL7InArchive> getAllHL7InArchives() throws APIException { return dao.getAllHL7InArchives(); } /** * @see org.openmrs.hl7.HL7Service#purgeHL7InArchive(org.openmrs.hl7.HL7InArchive) */ public void purgeHL7InArchive(HL7InArchive hl7InArchive) throws APIException { if (hl7InArchive != null) { dao.deleteHL7InArchive(hl7InArchive); } } /** * @see org.openmrs.hl7.HL7Service#saveHL7InArchive(org.openmrs.hl7.HL7InArchive) */ public HL7InArchive saveHL7InArchive(HL7InArchive hl7InArchive) throws APIException { if (hl7InArchive.getDateCreated() == null) { hl7InArchive.setDateCreated(new Date()); } return dao.saveHL7InArchive(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) */ @Transactional(readOnly = true) public HL7InArchive getHL7InArchive(Integer hl7InArchiveId) { return dao.getHL7InArchive(hl7InArchiveId); } /** * @see org.openmrs.hl7.HL7Service#getHL7InArchives() * @deprecated */ @Deprecated @Transactional(readOnly = true) public Collection<HL7InArchive> getHL7InArchives() { return Context.getHL7Service().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); } /** * get a list of archives to be migrated to the filesystem */ private List<HL7InArchive> getHL7InArchivesToMigrate() { return dao.getHL7InArchivesToMigrate(); } /** * @see org.openmrs.hl7.HL7Service#getAllHL7InErrors() */ @Transactional(readOnly = true) 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 { if (hl7InError.getDateCreated() == null) { 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) */ @Transactional(readOnly = true) public HL7InError getHL7InError(Integer hl7InErrorId) { return dao.getHL7InError(hl7InErrorId); } @Override @Transactional(readOnly = true) public HL7InError getHL7InErrorByUuid(String uuid) throws APIException { return dao.getHL7InErrorByUuid(uuid); } /** * @see org.openmrs.hl7.HL7Service#getHL7InErrors() * @deprecated */ @Deprecated @Transactional(readOnly = true) 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 */ @Transactional(readOnly = true) 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 = Integer.valueOf(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) */ @Transactional(readOnly = true) 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(Integer.valueOf(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 */ @Transactional(readOnly = true) 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 = Integer.valueOf(pointOfCare); Location l = Context.getLocationService().getLocation(locationId); if (l != null) { return 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 */ @Transactional(readOnly = true) 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 */ @Transactional(readOnly = true) 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(HL7Constants.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(HL7Constants.HL7_AUTHORITY_LOCAL)) { // the ID is internal (local) String idType = identifier.getIdentifierTypeCode().getValue(); try { if (idType.equals(HL7Constants.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(HL7Constants.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 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") && !"local".equals(hl7InQueue.getHL7Source().getName()) && "true".equals(Context.getAdministrationService().getGlobalProperty( OpenmrsConstants.GLOBAL_PROPERTY_IGNORE_MISSING_NONLOCAL_PATIENTS, "false"))) { skipError = true; } if (!skipError) { setFatalError(hl7InQueue, "Trouble parsing HL7 message (" + hl7InQueue.getHL7SourceKey() + ")", e); } } catch (Exception e) { setFatalError(hl7InQueue, "Exception while attempting to process HL7 In Queue (" + hl7InQueue.getHL7SourceKey() + ")", e); } 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 { log.error(cause); hl7InError.setErrorDetails(ExceptionUtils.getStackTrace(cause)); } Context.getHL7Service().saveHL7InError(hl7InError); Context.getHL7Service().purgeHL7InQueue(hl7InQueue); log.info(error, cause); } /** * @see org.openmrs.hl7.HL7Service#parseHL7String(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#getHL7InArchiveByUuid(java.lang.String) */ @Override @Transactional(readOnly = true) public HL7InArchive getHL7InArchiveByUuid(String uuid) throws APIException { if (Hl7InArchivesMigrateThread.isActive()) { throw new APIException("Hl7Service.cannot.fetch.archives", (Object[]) null); } return dao.getHL7InArchiveByUuid(uuid); } /** * @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("Hl7Service.invalid.messageName", (Object[]) null); } 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 (!"UUID".equals(assigningAuthority)) { 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()) { //If we have one identifier, set it as the preferred to make the validator happy. if (goodIdentifiers.size() == 1) { goodIdentifiers.get(0).setPreferred(true); } // 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 ("Y".equals(precision) || "L".equals(precision)) { 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 (OpenmrsUtil.nullSafeEquals(identifier.getAssigningAuthority().getNamespaceID().getValue(), "UUID")) { // check for duplicates if (found && !OpenmrsUtil.nullSafeEquals(identifier.getIDNumber().getValue(), uuid)) { throw new HL7Exception("multiple UUID values found"); } uuid = identifier.getIDNumber().getValue(); found = true; } } // returns null if not found return uuid; } /** * @see org.openmrs.hl7.HL7Service#loadHL7InArchiveData(List) */ public void loadHL7InArchiveData(List<HL7InArchive> archives) throws APIException { for (HL7InArchive archive : archives) { loadHL7InArchiveData(archive); } } /** * @see org.openmrs.hl7.HL7Service#loadHL7InArchiveData(HL7InArchive) */ public void loadHL7InArchiveData(HL7InArchive archive) throws APIException { // quit early if there is no archive to work with if (archive == null) { return; } // quit early if the message is not migrated or already loaded if (!OpenmrsUtil.nullSafeEquals(archive.getMessageState(), HL7Constants.HL7_STATUS_MIGRATED) || archive.isLoaded()) { return; } try { archive.setHL7Data(OpenmrsUtil.getFileAsString(new File(new URI(archive.getHL7Data())))); archive.setLoaded(true); } catch (URISyntaxException e) { throw new APIException("Hl7Service.malformed.archive.location", new Object[] { archive.getHL7Data() }, e); } catch (IOException e) { throw new APIException("Hl7Service.unable.convert.archive", new Object[] { archive.getHL7Data() }, e); } } /** * @see org.openmrs.hl7.HL7Service#migrateHl7InArchivesToFileSystem(Map) */ public void migrateHl7InArchivesToFileSystem(Map<String, Integer> progressStatusMap) throws APIException { int numberTransferred = 0; int numberOfFailedTransfers = 0; // HL7Constants.HL7_STATUS_ARCHIVED indicates the HL7 has been archived to the filesystem List<HL7InArchive> hl7InArchives = getHL7InArchivesToMigrate(); // while we still we have any archives to be processed, process them while (Hl7InArchivesMigrateThread.isActive() && Hl7InArchivesMigrateThread.getTransferStatus() == Status.RUNNING && hl7InArchives != null && hl7InArchives.size() > 0) { Iterator<HL7InArchive> iterator = hl7InArchives.iterator(); while (Hl7InArchivesMigrateThread.isActive() && Hl7InArchivesMigrateThread.getTransferStatus() == Status.RUNNING && iterator.hasNext()) { HL7InArchive archive = iterator.next(); try { migrateHL7InArchive(archive); progressStatusMap.put(HL7Constants.NUMBER_TRANSFERRED_KEY, numberTransferred++); } catch (DAOException e) { progressStatusMap.put(HL7Constants.NUMBER_OF_FAILED_TRANSFERS_KEY, numberOfFailedTransfers++); } } // fetch more archives to be processed hl7InArchives = getHL7InArchivesToMigrate(); } if (log.isDebugEnabled()) { log.debug("Transfer of HL7 archives has completed or has been stopped"); } } /** * moves data to the filesystem from an HL7InArchive * * @param archive * @throws APIException */ private void migrateHL7InArchive(HL7InArchive archive) throws APIException { if (archive == null) { throw new APIException("Hl7Service.migrate.null.archive", (Object[]) null); } if (!OpenmrsUtil.nullSafeEquals(archive.getMessageState(), HL7Constants.HL7_STATUS_PROCESSED)) { throw new APIException("Hl7Service.migrate.archive.state", (Object[]) null); } try { URI uri = writeHL7InArchiveToFileSystem(archive); archive.setHL7Data(uri.toString()); archive.setMessageState(HL7Constants.HL7_STATUS_MIGRATED); saveHL7InArchive(archive); } catch (APIException e) { throw new APIException("Hl7Service.migrate.archive", null, e); } } /** * writes a given hl7 archive to the file system * * @param hl7InArchive the hl7 archive to write to the file system */ private URI writeHL7InArchiveToFileSystem(HL7InArchive hl7InArchive) throws APIException { PrintWriter writer = null; File destinationDir = HL7Util.getHl7ArchivesDirectory(); try { // number formatter used to format month and day with zero padding DecimalFormat df = new DecimalFormat("00"); //write the archive to a separate file while grouping them according to //the year, month and date of month when they were stored in the archives table Calendar calendar = Calendar.getInstance(Context.getLocale()); calendar.setTime(hl7InArchive.getDateCreated()); //resolve the year folder from the date of creation of the archive File yearDir = new File(destinationDir, Integer.toString(calendar.get(Calendar.YEAR))); if (!yearDir.isDirectory()) { yearDir.mkdirs(); } //resolve the appropriate month folder File monthDir = new File(yearDir, df.format(calendar.get(Calendar.MONTH) + 1)); if (!monthDir.isDirectory()) { monthDir.mkdirs(); } //resolve the appropriate day of month folder File dayDir = new File(monthDir, df.format(calendar.get(Calendar.DAY_OF_MONTH))); if (!dayDir.isDirectory()) { dayDir.mkdirs(); } //use the uuid, source id and source key(if present) to generate the file name File fileToWriteTo = new File(dayDir, hl7InArchive.getUuid() + (StringUtils.isBlank(hl7InArchive.getHL7SourceKey()) ? "" : "_" + hl7InArchive.getHL7SourceKey()) + ".txt"); //write the hl7 data to the file writer = new PrintWriter(fileToWriteTo); writer.write(hl7InArchive.getHL7Data()); //check if there was an error while writing to the current file if (writer.checkError()) { log.warn("An Error occured while writing hl7 archive with id '" + hl7InArchive.getHL7InArchiveId() + "' to the file system"); throw new APIException("Hl7Service.write.no.error", (Object[]) null); } // hand back the URI for the file return fileToWriteTo.toURI(); } catch (FileNotFoundException e) { log.warn("Failed to write hl7 archive with id '" + hl7InArchive.getHL7InArchiveId() + "' to the file system ", e); throw new APIException("Hl7Service.write.error", null, e); } finally { if (writer != null) { writer.close(); } } } @Override @Transactional(readOnly = true) public HL7QueueItem getHl7QueueItemByUuid(String uuid) throws APIException { HL7QueueItem result = getHL7InQueueByUuid(uuid); if (result != null) { Context.hasPrivilege(PrivilegeConstants.PRIV_VIEW_HL7_IN_QUEUE); return result; } result = getHL7InErrorByUuid(uuid); if (result != null) { Context.hasPrivilege(PrivilegeConstants.PRIV_VIEW_HL7_IN_EXCEPTION); return result; } result = getHL7InArchiveByUuid(uuid); if (result != null) { Context.hasPrivilege(PrivilegeConstants.PRIV_VIEW_HL7_IN_ARCHIVE); return result; } return null; } }