Java tutorial
/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * 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. * * The Original Code is part of dcm4che, an implementation of DICOM(TM) in * Java(TM), available at http://sourceforge.net/projects/dcm4che. * * The Initial Developer of the Original Code is * TIANI Medgraph AG. * Portions created by the Initial Developer are Copyright (C) 2003-2005 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Gunter Zeilinger <gunter.zeilinger@tiani.com> * Franz Willer <franz.willer@gwi-ag.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ package org.dcm4chex.archive.hl7; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.net.Socket; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.Map.Entry; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; import javax.jms.ObjectMessage; import javax.management.Notification; import javax.management.NotificationListener; import javax.management.ObjectName; import javax.security.jacc.PolicyContext; import javax.servlet.http.HttpServletRequest; import javax.xml.transform.Templates; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.apache.log4j.Logger; import org.dcm4che.data.Dataset; import org.dcm4che.data.DcmObjectFactory; import org.dcm4che.dict.Tags; import org.dcm4che2.audit.message.ActiveParticipant; import org.dcm4che2.audit.message.AuditEvent; import org.dcm4che2.audit.message.AuditMessage; import org.dcm4che2.audit.message.ParticipantObject; import org.dcm4che2.audit.message.QueryMessage; import org.dcm4chex.archive.config.ForwardingRules; import org.dcm4chex.archive.config.RetryIntervalls; import org.dcm4chex.archive.ejb.interfaces.AEDTO; import org.dcm4chex.archive.ejb.interfaces.AEManager; import org.dcm4chex.archive.ejb.interfaces.AEManagerHome; import org.dcm4chex.archive.exceptions.ConfigurationException; import org.dcm4chex.archive.mbean.JMSDelegate; import org.dcm4chex.archive.mbean.TLSConfigDelegate; import org.dcm4chex.archive.mbean.TemplatesDelegate; import org.dcm4chex.archive.util.EJBHomeFactory; import org.dcm4chex.archive.util.XSLTUtils; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.DocumentSource; import org.dom4j.io.SAXContentHandler; import org.jboss.system.ServiceMBeanSupport; import org.regenstrief.xhl7.HL7XMLLiterate; import org.regenstrief.xhl7.HL7XMLReader; import org.regenstrief.xhl7.HL7XMLWriter; import org.regenstrief.xhl7.MLLPDriver; import org.regenstrief.xhl7.XMLWriter; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; public class HL7SendService extends ServiceMBeanSupport implements NotificationListener, MessageListener { private static final String WEB_REQUEST_KEY = "javax.servlet.http.HttpServletRequest"; private static final ParticipantObject.IDTypeCode PIX_QUERY = new ParticipantObject.IDTypeCode("ITI-9", "IHE Transactions", "PIX Query"); private static final AuditEvent.TypeCode PIX_QUERY_EVENT_TYPE = new AuditEvent.TypeCode("ITI-9", "IHE Transactions", "PIX Query"); private static final String LOCAL_HL7_AET = "LOCAL^LOCAL"; private static final String DATETIME_FORMAT = "yyyyMMddHHmmss"; private static final String FORWARD_XSL = "hl7forward"; private static final String XSL_EXT = ".xsl"; private static long msgCtrlid = System.currentTimeMillis(); private static long queryTag = msgCtrlid; private String queueName; private String sendingApplication; private String sendingFacility; private int acTimeout; private int soCloseDelay; private boolean auditPIXQuery; private ObjectName hl7ServerName; private TLSConfigDelegate tlsConfig = new TLSConfigDelegate(this); private RetryIntervalls retryIntervalls = new RetryIntervalls(); private ForwardingRules forwardingRules = new ForwardingRules(""); private int concurrency = 1; private JMSDelegate jmsDelegate = new JMSDelegate(this); protected TemplatesDelegate templates = new TemplatesDelegate(this); public String getCharsetName() { try { return (String) (this.getServer().getAttribute(hl7ServerName, "CharsetName")); } catch (Exception x) { throw new ConfigurationException(x); } } public final ObjectName getJmsServiceName() { return jmsDelegate.getJmsServiceName(); } public final void setJmsServiceName(ObjectName jmsServiceName) { jmsDelegate.setJmsServiceName(jmsServiceName); } public final int getConcurrency() { return concurrency; } public final void setConcurrency(int concurrency) throws Exception { if (concurrency <= 0) throw new IllegalArgumentException("Concurrency: " + concurrency); if (this.concurrency != concurrency) { final boolean restart = getState() == STARTED; if (restart) stop(); this.concurrency = concurrency; if (restart) start(); } } public final String getSendingApplication() { return sendingApplication; } public final void setSendingApplication(String sendingApplication) { this.sendingApplication = sendingApplication; } public final String getSendingFacility() { return sendingFacility; } public final void setSendingFacility(String sendingFacility) { this.sendingFacility = sendingFacility; } public String getRetryIntervalls() { return retryIntervalls.toString(); } public void setRetryIntervalls(String text) { retryIntervalls = new RetryIntervalls(text); } public final int getAcTimeout() { return acTimeout; } public final void setAcTimeout(int acTimeout) { this.acTimeout = acTimeout; } public final int getSoCloseDelay() { return soCloseDelay; } public final void setSoCloseDelay(int soCloseDelay) { this.soCloseDelay = soCloseDelay; } public final boolean isAuditPIXQuery() { return auditPIXQuery; } public final void setAuditPIXQuery(boolean auditPIXQuery) { this.auditPIXQuery = auditPIXQuery; } public final String getForwardTemplateDir() { return templates.getConfigDir(); } public final void setForwardTemplateDir(String path) { templates.setConfigDir(path); } public final ObjectName getTLSConfigName() { return tlsConfig.getTLSConfigName(); } public final void setTLSConfigName(ObjectName tlsConfigName) { tlsConfig.setTLSConfigName(tlsConfigName); } public final String getQueueName() { return queueName; } public final void setQueueName(String queueName) { this.queueName = queueName; } public final ObjectName getHL7ServerName() { return hl7ServerName; } public final void setHL7ServerName(ObjectName hl7ServerName) { this.hl7ServerName = hl7ServerName; } public final String getForwardingRules() { return forwardingRules.toString(); } public final void setForwardingRules(String s) { this.forwardingRules = new ForwardingRules(s); } public final ObjectName getTemplatesServiceName() { return templates.getTemplatesServiceName(); } public final void setTemplatesServiceName(ObjectName serviceName) { templates.setTemplatesServiceName(serviceName); } protected void startService() throws Exception { jmsDelegate.startListening(queueName, this, concurrency); server.addNotificationListener(hl7ServerName, this, HL7ServerService.NOTIF_FILTER, null); } protected void stopService() throws Exception { server.removeNotificationListener(hl7ServerName, this, HL7ServerService.NOTIF_FILTER, null); jmsDelegate.stopListening(queueName); } public void handleNotification(Notification notif, Object handback) { Object[] hl7msg = (Object[]) notif.getUserData(); forward((byte[]) hl7msg[0], (Document) hl7msg[1]); } public int forward(byte[] hl7msg) { XMLReader xmlReader = new HL7XMLReader(); SAXContentHandler hl7in = new SAXContentHandler(); xmlReader.setContentHandler(hl7in); try { InputSource in = new InputSource( new InputStreamReader(new ByteArrayInputStream(hl7msg), getCharsetName())); xmlReader.parse(in); } catch (Exception e) { log.error("Failed to parse HL7 message", e); return -1; } return forward(hl7msg, hl7in.getDocument()); } private int forward(byte[] hl7msg, Document msg) { MSH msh = new MSH(msg); Map<String, String[]> param = new HashMap<String, String[]>(); String receiving = msh.receivingApplication + '^' + msh.receivingFacility; param.put("sending", new String[] { msh.sendingApplication + '^' + msh.sendingFacility }); param.put("receiving", new String[] { receiving }); param.put("msgtype", new String[] { msh.messageType + '^' + msh.triggerEvent }); String[] dests = forwardingRules.getForwardDestinationsFor(param); int count = 0; for (int i = 0; i < dests.length; i++) { hl7msg = preprocessForward(hl7msg, msg, msh, dests[i]); HL7SendOrder order = new HL7SendOrder(hl7msg, dests[i]); try { order.processOrderProperties(msh); } catch (Exception e) { log.error("Failed to process order properties for " + order, e); } try { log.info("Scheduling " + order); jmsDelegate.queue(queueName, order, Message.DEFAULT_PRIORITY, 0L); ++count; } catch (Exception e) { log.error("Failed to schedule " + order, e); } } return count; } private byte[] preprocessForward(byte[] hl7msg, Document msg, MSH msh, String receiving) { String[] variations = new String[] { "_" + msh.messageType + "^" + msh.triggerEvent, "_" + msh.messageType, "" }; Templates xslt = templates.findTemplates(new String[] { receiving }, FORWARD_XSL, variations, XSL_EXT); if (xslt != null) { log.info("Transform HL7 message with hl7forward stylesheet!"); try { Transformer t = xslt.newTransformer(); ByteArrayOutputStream bos = new ByteArrayOutputStream(hl7msg.length); XMLWriter xmlWriter = new HL7XMLWriter(new OutputStreamWriter(bos, getCharsetName())); t.transform(new DocumentSource(msg), new SAXResult(xmlWriter.getContentHandler())); hl7msg = bos.toByteArray(); } catch (Exception x) { log.error("Can not apply hl7forward stylesheet!", x); } } return hl7msg; } public void onMessage(Message message) { ObjectMessage om = (ObjectMessage) message; try { HL7SendOrder order = (HL7SendOrder) om.getObject(); try { log.info("Start processing " + order); sendTo(order.getHL7Message(), order.getReceiving()); log.info("Finished processing " + order); } catch (Exception e) { order.setThrowable(e); final int failureCount = order.getFailureCount() + 1; order.setFailureCount(failureCount); final long delay = retryIntervalls.getIntervall(failureCount); if (delay == -1L) { log.error("Give up to process " + order); jmsDelegate.fail(queueName, order); } else { log.warn("Failed to process " + order + ". Scheduling retry.", e); jmsDelegate.queue(queueName, order, 0, System.currentTimeMillis() + delay); } } } catch (JMSException e) { log.error("jms error during processing message: " + message, e); } catch (Throwable e) { log.error("unexpected error during processing message: " + message, e); } } public Document invoke(byte[] message, String receiver) throws Exception { AEDTO localAE = new AEDTO(); localAE.setTitle(receiver); localAE.setHostName("127.0.0.1"); localAE.setPort(getLocalHL7Port()); AEDTO remoteAE = LOCAL_HL7_AET.equals(receiver) ? localAE : aeMgt().findByAET(receiver); Socket s = tlsConfig.createSocket(localAE, remoteAE); boolean ignoreMissingAck = false; try { MLLPDriver mllpDriver = new MLLPDriver(s.getInputStream(), s.getOutputStream(), true); writeMessage(message, receiver, mllpDriver.getOutputStream()); mllpDriver.turn(); ignoreMissingAck = Boolean.getBoolean("org.dcm4che.hl7.ignoreMissingAck"); if (acTimeout > 0) { s.setSoTimeout(acTimeout); } if (!mllpDriver.hasMoreInput()) { throw new IOException( "Receiver " + receiver + " closed socket " + s + " during waiting on response."); } return readMessage(mllpDriver.getInputStream()); } catch (Exception x) { if (ignoreMissingAck) { log.info("Missing Acknowledge ignored! return null as response message."); return null; } throw x; } finally { if (soCloseDelay > 0) try { Thread.sleep(soCloseDelay); } catch (InterruptedException ignore) { } s.close(); } } public void sendTo(byte[] message, String receiver) throws Exception { Document rsp = invoke(message, receiver); if (rsp != null) checkResponse(rsp); } private void checkResponse(Document rsp) throws HL7Exception { MSH msh = new MSH(rsp); if ("ACK".equals(msh.messageType)) { ACK ack = new ACK(rsp); if (!("AA".equals(ack.acknowledgmentCode) || "CA".equals(ack.acknowledgmentCode))) throw new HL7Exception(ack.acknowledgmentCode, ack.textMessage); } else { log.warn("Unsupport response message type: " + msh.messageType + '^' + msh.triggerEvent + ". Assume successful message forward."); } } /** * @return */ private int getLocalHL7Port() { try { return ((Integer) this.getServer().getAttribute(hl7ServerName, "Port")).intValue(); } catch (Exception x) { log.error("Cant get local HL7 port!", x); return -1; } } private Document readMessage(InputStream mllpIn) throws IOException, SAXException { InputSource in = new InputSource(mllpIn); in.setEncoding(getCharsetName()); XMLReader xmlReader = new HL7XMLReader(); SAXContentHandler hl7in = new SAXContentHandler(); xmlReader.setContentHandler(hl7in); xmlReader.parse(in); Document msg = hl7in.getDocument(); return msg; } private void writeMessage(byte[] message, String receiving, OutputStream out) throws UnsupportedEncodingException, IOException { final String charsetName = getCharsetName(); int offs = writePartTo(out, message, '|', 0, 4); //write MSH 1-4 final int delim = receiving.indexOf('^'); out.write(receiving.substring(0, delim).getBytes(charsetName)); out.write('|'); out.write(receiving.substring(delim + 1).getBytes(charsetName)); while (message[++offs] != '|') { } //skip MSH-5 receiving application while (message[++offs] != '|') { } //skip MSH-6 receiving facility // write remaining message out.write(message, offs, message.length - offs); } private int writePartTo(OutputStream out, byte[] ba, char b, int offs, int count) throws IOException { for (int i = offs; i < ba.length; i++) { if (ba[i] == b) { count--; if (count == 0) { out.write(ba, offs, i - offs + 1); return i; } } } return -1; } public void sendHL7PatientXXX(Dataset ds, String msgType, String sending, String receiving, boolean useForward) throws Exception { String timestamp = new SimpleDateFormat(DATETIME_FORMAT).format(new Date()); StringBuffer sb = makeMSH(timestamp, msgType, sending, receiving, ++msgCtrlid, "2.3.1");// get MSH for patient information update (ADT^A08) addEVN(sb, timestamp); addPID(sb, ds); sb.append("\rPV1||||||||||||||||||||||||||||||||||||||||||||||||||||\r"); // PatientClass(2),VisitNr(19) and VisitIndicator(51) ??? final String charsetName = getCharsetName(); if (useForward) { forward(sb.toString().getBytes(charsetName)); } else { sendTo(sb.toString().getBytes(charsetName), receiving); } } public void sendHL7PatientMerge(Dataset dsDominant, Dataset[] priorPats, String sending, String receiving, boolean useForward) throws Exception { String timestamp = new SimpleDateFormat(DATETIME_FORMAT).format(new Date()); StringBuffer sb = makeMSH(timestamp, "ADT^A40", sending, receiving, ++msgCtrlid, "2.3.1");// get MSH for patient merge (ADT^A40) addEVN(sb, timestamp); addPID(sb, dsDominant); int SBlen = sb.length(); final String charsetName = getCharsetName(); for (int i = 0, len = priorPats.length; i < len; i++) { sb.setLength(SBlen); addMRG(sb, priorPats[i]); sb.append('\r'); if (useForward) { forward(sb.toString().getBytes(charsetName)); } else { sendTo(sb.toString().getBytes(charsetName), receiving); } } } /** * Sends a PDQ query with the parameters being contains in the input Map, and the output * being contained in a List of Maps of Strings to Strings - the data being the result of the * query. The last list element is a continuation element if count is non-zero. */ public List<Map<String, String>> sendQBP_Q22(String pdqManager, Map<String, String> query, String domain, int count, String continuation) throws Exception { String timestamp = new SimpleDateFormat(DATETIME_FORMAT).format(new Date()); StringBuffer sb = makeMSH(timestamp, "QBP^Q22", null, pdqManager, ++msgCtrlid, "2.5"); String qpd = makeQPD_Q22(query, domain); sb.append('\r').append(qpd).append("\rRCP|I||||||\r"); String s = sb.toString(); log.info("Query PDQ Manager " + pdqManager + ":\n" + s.replace('\r', '\n')); final String charsetName = getCharsetName(); Document msg = invoke(s.getBytes(charsetName), pdqManager); log.info("PDQ Query returns:"); logMessage(msg); List<Map<String, String>> ret = parsePDQ(msg); log.info("Returning PDQ response now:" + toString(ret)); return ret; } public static final String toString(List<Map<String, String>> lst) { StringBuffer sb = new StringBuffer("["); boolean listFirst = true; for (Map<String, String> map : lst) { if (listFirst) listFirst = false; else sb.append(",\n "); sb.append("{"); boolean first = true; for (Map.Entry<String, String> me : map.entrySet()) { if (first) first = false; else sb.append(", "); sb.append(me.getKey()).append(":\"").append(me.getValue()).append("\""); } sb.append("}"); } sb.append(']'); return sb.toString(); } private static String[] FIELD_NAMES = new String[] { null, // PID-1 null, null, "PatientIDList", null, // PID-5 "PatientName", "MothersMaidenName", "PatientBirthDate", "PatientSex", // PID-9 "PatientAlias", "Race", "PatientAddress", "CountyCode", // PID-13 "PhoneNumberHome", "PhoneNumberBusiness", "PrimaryLanguage", "MaritalStatus", // PID-17 "Religion", "PatientAccountNumber", "SSNNumber", "DriversLicenseNumber", // PID-21 "MothersIdentifier", "EthnicGroup", "BirthPlace", "MultipleBirthIndicator", //PID-25 "BirthOrder", "Citizenship", "VeteransMilitaryStatus", "Nationality", //PID-29 "PatientDeathDateTime", "PatientDeathIndicator", "IdentityUnknownIndicator", "IdentityReliabilityCode", //PID-33 "LastUpdateDateTime", "LastUpdateFacility", "SpeciesCode", "BreedCode", //PID-37 "Strain", "ProductionClassCode", "TribalCitizenship", }; /** Parse the PID entries into a list of maps */ public static final List<Map<String, String>> parsePDQ(Document msg) { List<Map<String, String>> ret = new ArrayList<Map<String, String>>(); Element root = msg.getRootElement(); List<?> content = root.content(); for (Object c : content) { if (!(c instanceof Element)) continue; Element e = (Element) c; if (e.getName().equals("PID")) { Map<String, String> pid = new HashMap<String, String>(); pid.put("Type", "Patient"); List<?> fields = e.elements(HL7XMLLiterate.TAG_FIELD); int pidNo = 0; for (Object f : fields) { pidNo++; if (pidNo >= FIELD_NAMES.length) continue; String fieldName = FIELD_NAMES[pidNo]; if (fieldName == null) continue; Element field = (Element) f; if (field.isTextOnly()) { String txt = field.getText(); if (txt == null || txt.length() == 0) continue; pid.put(fieldName, txt); continue; } if (pidNo == 3) { List<?> comps = field.elements(HL7XMLLiterate.TAG_COMPONENT); if (comps.size() < 3) { throw new IllegalArgumentException("Missing Authority in PID-3"); } Element authority = (Element) comps.get(2); List<?> authorityUID = authority.elements(HL7XMLLiterate.TAG_SUBCOMPONENT); pid.put("PatientID", field.getText()); StringBuffer issuer = new StringBuffer(authority.getText()); for (int i = 0; i < authorityUID.size(); i++) { issuer.append("&").append(((Element) authorityUID.get(i)).getText()); } pid.put("IssuerOfPatientID", issuer.toString()); continue; } if (pidNo == 5) { String name = field.getText() + "^" + field.elementText(HL7XMLLiterate.TAG_COMPONENT); pid.put(fieldName, name); continue; } pid.put(fieldName, field.asXML()); } ret.add(pid); } } return ret; } /** * Sends a PDQ query with the parameters being contains in the input Map, and the output * being a multi-line set of results. Intended for test purposes, use the above method for * parsing the results. */ public String showQBP_Q22(String pdqManager, String query, String domain) throws Exception { String timestamp = new SimpleDateFormat(DATETIME_FORMAT).format(new Date()); StringBuffer sb = makeMSH(timestamp, "QBP^Q22", null, pdqManager, ++msgCtrlid, "2.5"); String qpd = makeQPD_Q22(query, domain); sb.append('\r').append(qpd).append("\rRCP|I||||||\r"); String s = sb.toString(); log.info("Query PDQ Manager " + pdqManager + ":\n" + s.replace('\r', '\n')); final String charsetName = getCharsetName(); Document msg = invoke(s.getBytes(charsetName), pdqManager); log.info("PIX Query returns:"); logMessage(msg); List<Map<String, String>> results = parsePDQ(msg); return "PDQ query results:\n" + toString(results); } /** Sends a PIX query on the given patient ID and issuer, asking for * items from domain, or all id's if domains is null. * @param pixManager * @param pixQueryName * @param patientID * @param issuer * @param domains * @return * @throws Exception */ @SuppressWarnings("unchecked") public List<String[]> sendQBP_Q23(String pixManager, String pixQueryName, String patientID, String issuer, String[] domains) throws Exception { String timestamp = new SimpleDateFormat(DATETIME_FORMAT).format(new Date()); long msgCtrlid = ++HL7SendService.msgCtrlid; long queryTag = ++HL7SendService.queryTag; StringBuffer sb = makeMSH(timestamp, "QBP^Q23^QBP_Q21", null, pixManager, msgCtrlid, "2.5"); String qpd = makeQPD(pixQueryName, queryTag, patientID, issuer, domains); sb.append('\r').append(qpd).append("\rRCP|I||||||\r"); String s = sb.toString(); log.info("Query PIX Manager " + pixManager + ":\n" + s.replace('\r', '\n')); final String charsetName = getCharsetName(); try { Document msg = invoke(s.getBytes(charsetName), pixManager); log.info("PIX Query returns:"); logMessage(msg); MSH msh = new MSH(msg); if (!"RSP".equals(msh.messageType) || !"K23".equals(msh.triggerEvent)) { String prompt = "Unsupport response message type: " + msh.messageType + '^' + msh.triggerEvent; log.error(prompt); throw new IOException(prompt); } RSP rsp = new RSP(msg); if ("AE".equals(rsp.acknowledgmentCode)) { ERR err = new ERR(msg); if (err.isUnknownKeyIdentifier()) return null; } if (!"AA".equals(rsp.acknowledgmentCode)) { log.error("PIX Query fails with code " + rsp.acknowledgmentCode + " - " + rsp.textMessage); throw new HL7Exception(rsp.acknowledgmentCode, rsp.textMessage); } if (auditPIXQuery) { auditPIXQuery(pixManager, patientID, issuer, msgCtrlid, queryTag, qpd, null); } return rsp.getPatientIDs(); } catch (Exception e) { if (auditPIXQuery) { auditPIXQuery(pixManager, patientID, issuer, msgCtrlid, queryTag, qpd, e); } throw e; } } private String makeQPD(String pixQueryName, long queryTag, String patientID, String issuer, String[] domains) { StringBuffer sb = new StringBuffer("QPD|"); sb.append(pixQueryName).append('|'); sb.append(queryTag).append('|'); sb.append(patientID).append("^^^").append(issuer); if (domains != null && domains.length > 0) { sb.append("|^^^").append(domains[0]); for (int i = 1; i < domains.length; i++) { sb.append("~^^^").append(domains[i]);// ~ is repeat delimiter // used in makeMSH } } return sb.toString(); } private String makeQPD_Q22(Map<String, String> params, String domain) { StringBuffer sb = new StringBuffer("QPD|IHE PDQ Query|"); sb.append((++queryTag)).append('|'); Iterator<Entry<String, String>> it = params.entrySet().iterator(); String sep = null; while (it.hasNext()) { Map.Entry<String, String> me = it.next(); if (sep == null) sep = "~"; else sb.append(sep); sb.append('@').append(me.getKey()).append("^").append(me.getValue()); } sb.append("|||||"); if (domain != null) sb.append("^^^").append(domain); return sb.toString(); } /** Make the query assuming the string is already formatted for HL7 PDQ query. */ private String makeQPD_Q22(String query, String domain) { StringBuffer sb = new StringBuffer("QPD|IHE PDQ Query|"); sb.append((++queryTag)).append('|').append(query); if (domain != null) sb.append("|||||^^^").append(domain); return sb.toString(); } private void auditPIXQuery(String pixManager, String patientID, String issuer, long msgCtrlid, long queryTag, String qpd, Exception e) { try { QueryMessage msg = new QueryMessage(); msg.getAuditEvent().addEventTypeCode(PIX_QUERY_EVENT_TYPE); ActiveParticipant source1 = ActiveParticipant.createActivePerson( sendingFacility + '|' + sendingApplication, // userID AuditMessage.getProcessID(), // altUserID null, // userName AuditMessage.getLocalHostName(), // napID true); // requester source1.addRoleIDCode(ActiveParticipant.RoleIDCode.SOURCE); msg.addActiveParticipant(source1); HttpServletRequest httprq = (HttpServletRequest) PolicyContext.getContext(WEB_REQUEST_KEY); if (httprq != null) { String remoteUser = httprq.getRemoteUser(); String remoteHost = httprq.getRemoteHost(); if (remoteUser != null) { ActiveParticipant source2 = ActiveParticipant.createActivePerson(remoteUser, // userID null, // altUserID remoteUser, // userName remoteHost, // napID true); // requester msg.addActiveParticipant(source2); } } AEDTO pixManagerInfo = aeMgt().findByAET(pixManager); ActiveParticipant dest = ActiveParticipant.createActivePerson(pixManager.replace('^', '|'), // userID null, // altUserID null, // userName pixManagerInfo.getHostName(), // napID false); // requester dest.addRoleIDCode(ActiveParticipant.RoleIDCode.DESTINATION); msg.addActiveParticipant(dest); ParticipantObject patObj = new ParticipantObject(patientID + "^^^" + issuer, ParticipantObject.IDTypeCode.PATIENT_ID); patObj.setParticipantObjectTypeCode(ParticipantObject.TypeCode.PERSON); patObj.setParticipantObjectTypeCodeRole(ParticipantObject.TypeCodeRole.PATIENT); msg.addParticipantObject(patObj); ParticipantObject queryObj = new ParticipantObject(String.valueOf(queryTag), PIX_QUERY); queryObj.setParticipantObjectTypeCode(ParticipantObject.TypeCode.SYSTEM); queryObj.setParticipantObjectTypeCodeRole(ParticipantObject.TypeCodeRole.QUERY); queryObj.setParticipantObjectQuery(qpd.getBytes("UTF-8")); queryObj.addParticipantObjectDetail("MSH-10", String.valueOf(msgCtrlid)); msg.addParticipantObject(queryObj); Logger auditlog = Logger.getLogger("auditlog"); if (e == null) { auditlog.info(msg); } else { msg.setOutcomeIndicator(AuditEvent.OutcomeIndicator.MAJOR_FAILURE); auditlog.warn(msg); } } catch (Exception e2) { log.warn("Failed to send Audit Log Used message", e2); } } private void logMessage(Document msg) { try { server.invoke(hl7ServerName, "logMessage", new Object[] { msg }, new String[] { Document.class.getName() }); } catch (Exception e) { log.warn("Failed to log message", e); } } private StringBuffer makeMSH(String timestamp, String msgType, String sending, String receiving, long msgCtrlid, String version) { StringBuffer sb = new StringBuffer(); sb.append("MSH|^~\\&|"); int delim; if (sending == null || sending.trim().length() == 0) { sb.append(getSendingApplication()).append("|"); sb.append(getSendingFacility()).append("|"); } else { delim = sending.indexOf('^'); sb.append(sending.substring(0, delim)).append("|"); sb.append(sending.substring(delim + 1)).append("|"); } delim = receiving.indexOf('^'); sb.append(receiving.substring(0, delim)).append("|"); sb.append(receiving.substring(delim + 1)).append("|"); sb.append(timestamp).append("||"); sb.append(msgType).append("|"); sb.append(msgCtrlid).append("|P|"); sb.append(version).append("||||||||"); return sb; } private void addEVN(StringBuffer sb, String timeStamp) { sb.append("\rEVN||").append(timeStamp).append("||||").append(timeStamp); } /** * @param sb * PID will be added to this StringBuffer. * @param ds * Dataset to get PID informations */ private void addPID(StringBuffer sb, Dataset ds) { String s; Date d; sb.append("\rPID|||"); appendPatIDwithIssuer(sb, ds); sb.append("||"); addPersonName(sb, ds.getString(Tags.PatientName)); sb.append("||"); d = ds.getDateTime(Tags.PatientBirthDate, Tags.PatientBirthTime); if (d != null) sb.append(new SimpleDateFormat(DATETIME_FORMAT).format(d)); sb.append("|"); s = ds.getString(Tags.PatientSex); if (s != null) sb.append(s); sb.append("||||||||||||||||||||||"); // patient Account number ???(field 18) } // concerns different order of name suffix, prefix in HL7 XPN compared to // DICOM PN private void addPersonName(StringBuffer sb, final String patName) { StringTokenizer stk = new StringTokenizer(patName, "^", true); for (int i = 0; i < 6 && stk.hasMoreTokens(); ++i) { sb.append(stk.nextToken()); } if (stk.hasMoreTokens()) { String prefix = stk.nextToken(); if (stk.hasMoreTokens()) { stk.nextToken(); // skip delim if (stk.hasMoreTokens()) { sb.append(stk.nextToken()); // name suffix } } sb.append('^').append(prefix); } } private void appendPatIDwithIssuer(StringBuffer sb, Dataset ds) { sb.append(ds.getString(Tags.PatientID)); String s = ds.getString(Tags.IssuerOfPatientID); if (s != null) sb.append("^^^").append(s); // issuer of patient ID } /** * @param sb * @param ds */ private void addMRG(StringBuffer sb, Dataset ds) { sb.append("\rMRG|"); appendPatIDwithIssuer(sb, ds); sb.append("||||||"); String name = ds.getString(Tags.PatientName); sb.append(name != null ? name : "patName"); } private AEManager aeMgt() throws Exception { AEManagerHome home = (AEManagerHome) EJBHomeFactory.getFactory().lookup(AEManagerHome.class, AEManagerHome.JNDI_NAME); return home.create(); } public void sendHL7File(File file, String receiver) throws Exception { FileInputStream fis = new FileInputStream(file); byte[] msg = new byte[(int) file.length()]; fis.read(msg); this.sendTo(msg, receiver); } public boolean sendHl7FromDataset(String dsFilename, String xslFilename, String sender, String receiver) throws IOException, TransformerConfigurationException, TransformerFactoryConfigurationError { Dataset ds = DcmObjectFactory.getInstance().newDataset(); ds.readFile(new File(dsFilename), null, Tags.PixelData); Templates tpl = TransformerFactory.newInstance().newTemplates(new StreamSource(new File(xslFilename))); return sendHl7FromDataset(ds, tpl, sender, receiver); } public boolean sendHl7FromDataset(Dataset ds, Templates tpl, String sender, String receiver) { Socket s = null; try { AEDTO localAE = new AEDTO(); localAE.setTitle(receiver); localAE.setHostName("127.0.0.1"); localAE.setPort(getLocalHL7Port()); AEDTO remoteAE = LOCAL_HL7_AET.equals(receiver) ? localAE : aeMgt().findByAET(receiver); s = tlsConfig.createSocket(localAE, remoteAE); MLLPDriver mllpDriver = new MLLPDriver(s.getInputStream(), s.getOutputStream(), true); OutputStream out = mllpDriver.getOutputStream(); writeDatasetAsHL7msg(ds, tpl, sender, receiver, out); mllpDriver.turn(); if (acTimeout > 0) { s.setSoTimeout(acTimeout); } if (!mllpDriver.hasMoreInput()) { throw new IOException( "Receiver " + receiver + " closed socket " + s + " during waiting on response."); } Document rsp = readMessage(mllpDriver.getInputStream()); checkResponse(rsp); return true; } catch (Exception x) { log.warn("Sending HL7 message from Dataset failed! Reason:" + x.getMessage(), x); final long delay = retryIntervalls.getIntervall(1); if (delay != -1L) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { writeDatasetAsHL7msg(ds, tpl, sender, receiver, baos); byte[] msg = baos.toByteArray(); if (msg.length > 3 && msg[0] == 'M' && msg[1] == 'S' && msg[2] == 'H') { HL7SendOrder order = new HL7SendOrder(msg, receiver); order.setFailureCount(1); jmsDelegate.queue(queueName, order, 0, System.currentTimeMillis() + delay); } else { log.error( "Message generated from Dataset is not valid (Does not start with MSH!) and will not be scheduled for retry!"); log.debug("Dataset:"); log.debug(ds); } } catch (Exception e) { log.error("Cannot schedule 'retry' order for failed 'send HL7 message from Dataset'!", e); return false; } } return false; } finally { if (s != null) { if (soCloseDelay > 0) try { Thread.sleep(soCloseDelay); } catch (InterruptedException ignore) { } try { s.close(); } catch (IOException ignore) { } } } } private void writeDatasetAsHL7msg(Dataset ds, Templates tpl, String sender, String receiver, OutputStream out) throws TransformerConfigurationException, IOException { TransformerHandler th = getTransformHandler(tpl, sender, receiver); th.setResult(new StreamResult(out)); ds.writeDataset2(th, null, null, 64, null); } private TransformerHandler getTransformHandler(Templates tpl, String sender, String receiver) throws TransformerConfigurationException { TransformerHandler th = XSLTUtils.transformerFactory.newTransformerHandler(tpl); XSLTUtils.setDateParameters(th); Transformer t = th.getTransformer(); final SimpleDateFormat tsFormat = new SimpleDateFormat("yyyyMMddHHmmss.SSS"); t.setParameter("messageControlID", String.valueOf(++msgCtrlid)); t.setParameter("messageDateTime", tsFormat.format(new Date())); int pos = receiver.indexOf('^'); t.setParameter("receivingApplication", receiver.substring(0, pos++)); t.setParameter("receivingFacility", receiver.substring(pos)); if (sender != null) { pos = sender.indexOf('^'); t.setParameter("sendingApplication", sender.substring(0, pos++)); t.setParameter("sendingFacility", sender.substring(pos)); } else { t.setParameter("sendingApplication", sendingApplication); t.setParameter("sendingFacility", sendingFacility); } return th; } }