/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.camel.component.mllp; import java.io.IOException; import java.io.InputStream; import java.net.BindException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import org.apache.camel.Exchange; import org.apache.camel.ExchangePattern; import org.apache.camel.Message; import org.apache.camel.Processor; import org.apache.camel.api.management.ManagedOperation; import org.apache.camel.api.management.ManagedResource; import org.apache.camel.component.mllp.impl.Hl7Util; import org.apache.camel.component.mllp.impl.MllpBufferedSocketWriter; import org.apache.camel.component.mllp.impl.MllpSocketReader; import org.apache.camel.component.mllp.impl.MllpSocketUtil; import org.apache.camel.component.mllp.impl.MllpSocketWriter; import org.apache.camel.converter.IOConverter; import org.apache.camel.impl.DefaultConsumer; import org.apache.camel.processor.mllp.Hl7AcknowledgementGenerationException; import org.apache.camel.processor.mllp.Hl7AcknowledgementGenerator; import org.apache.camel.util.IOHelper; import org.slf4j.MDC; import static org.apache.camel.component.mllp.MllpConstants.MLLP_ACKNOWLEDGEMENT; import static org.apache.camel.component.mllp.MllpConstants.MLLP_ACKNOWLEDGEMENT_EXCEPTION; import static org.apache.camel.component.mllp.MllpConstants.MLLP_ACKNOWLEDGEMENT_STRING; import static org.apache.camel.component.mllp.MllpConstants.MLLP_ACKNOWLEDGEMENT_TYPE; import static org.apache.camel.component.mllp.MllpConstants.MLLP_AUTO_ACKNOWLEDGE; import static org.apache.camel.component.mllp.MllpConstants.MLLP_CHARSET; import static org.apache.camel.component.mllp.MllpConstants.MLLP_CLOSE_CONNECTION_AFTER_SEND; import static org.apache.camel.component.mllp.MllpConstants.MLLP_CLOSE_CONNECTION_BEFORE_SEND; import static org.apache.camel.component.mllp.MllpConstants.MLLP_EVENT_TYPE; import static org.apache.camel.component.mllp.MllpConstants.MLLP_LOCAL_ADDRESS; import static org.apache.camel.component.mllp.MllpConstants.MLLP_MESSAGE_CONTROL; import static org.apache.camel.component.mllp.MllpConstants.MLLP_MESSAGE_TYPE; import static org.apache.camel.component.mllp.MllpConstants.MLLP_PROCESSING_ID; import static org.apache.camel.component.mllp.MllpConstants.MLLP_RECEIVING_APPLICATION; import static org.apache.camel.component.mllp.MllpConstants.MLLP_RECEIVING_FACILITY; import static org.apache.camel.component.mllp.MllpConstants.MLLP_REMOTE_ADDRESS; import static org.apache.camel.component.mllp.MllpConstants.MLLP_RESET_CONNECTION_AFTER_SEND; import static org.apache.camel.component.mllp.MllpConstants.MLLP_RESET_CONNECTION_BEFORE_SEND; import static org.apache.camel.component.mllp.MllpConstants.MLLP_SECURITY; import static org.apache.camel.component.mllp.MllpConstants.MLLP_SENDING_APPLICATION; import static org.apache.camel.component.mllp.MllpConstants.MLLP_SENDING_FACILITY; import static org.apache.camel.component.mllp.MllpConstants.MLLP_TIMESTAMP; import static org.apache.camel.component.mllp.MllpConstants.MLLP_TRIGGER_EVENT; import static org.apache.camel.component.mllp.MllpConstants.MLLP_VERSION_ID; import static org.apache.camel.component.mllp.MllpEndpoint.SEGMENT_DELIMITER; /** * The MLLP consumer. */ @ManagedResource(description = "MllpTcpServer Consumer") public class MllpTcpServerConsumer extends DefaultConsumer { public static final int SOCKET_STARTUP_TEST_WAIT = 100; public static final int SOCKET_STARTUP_TEST_READ_TIMEOUT = 250; ServerSocketThread serverSocketThread; List<ClientSocketThread> clientThreads = new LinkedList<>(); Hl7AcknowledgementGenerator acknowledgementGenerator = new Hl7AcknowledgementGenerator(); private final MllpEndpoint endpoint; public MllpTcpServerConsumer(MllpEndpoint endpoint, Processor processor) { super(endpoint, processor); log.trace("MllpTcpServerConsumer(endpoint, processor)"); this.endpoint = endpoint; } @Override protected void doStart() throws Exception { log.debug("doStart() - creating acceptor thread"); startMllpConsumer(); super.doStart(); } @ManagedOperation(description = "Check server connection") public boolean managedCheckConnection() { boolean isValid = true; try { InetSocketAddress socketAddress; if (null == endpoint.getHostname()) { socketAddress = new InetSocketAddress(endpoint.getPort()); } else { socketAddress = new InetSocketAddress(endpoint.getHostname(), endpoint.getPort()); } Socket checkSocket = new Socket(); checkSocket.connect(socketAddress, 100); checkSocket.close(); } catch (Exception e) { isValid = false; log.debug("JMX check connection: {}", e); } return isValid; } @ManagedOperation(description = "Starts serverSocket thread and waits for requests") public void startMllpConsumer() throws IOException, InterruptedException { ServerSocket serverSocket = new ServerSocket(); if (null != endpoint.receiveBufferSize) { serverSocket.setReceiveBufferSize(endpoint.receiveBufferSize); } serverSocket.setReuseAddress(endpoint.reuseAddress); // Accept Timeout serverSocket.setSoTimeout(endpoint.acceptTimeout); InetSocketAddress socketAddress; if (null == endpoint.getHostname()) { socketAddress = new InetSocketAddress(endpoint.getPort()); } else { socketAddress = new InetSocketAddress(endpoint.getHostname(), endpoint.getPort()); } long startTicks = System.currentTimeMillis(); do { try { serverSocket.bind(socketAddress, endpoint.backlog); } catch (BindException bindException) { if (System.currentTimeMillis() > startTicks + endpoint.getBindTimeout()) { log.error("Failed to bind to address {} within timeout {}", socketAddress, endpoint.getBindTimeout()); throw bindException; } else { log.warn("Failed to bind to address {} - retrying in {} milliseconds", socketAddress, endpoint.getBindRetryInterval()); Thread.sleep(endpoint.getBindRetryInterval()); } } } while (!serverSocket.isBound()); serverSocketThread = new ServerSocketThread(serverSocket); serverSocketThread.start(); } @Override protected void doStop() throws Exception { log.debug("doStop()"); stopMllpConsumer(); super.doStop(); } @ManagedOperation(description = "Stops client threads and serverSocket thread") public void stopMllpConsumer() { // Close any client sockets that are currently open for (ClientSocketThread clientSocketThread: clientThreads) { clientSocketThread.interrupt(); } switch (serverSocketThread.getState()) { case TERMINATED: // This is what we hope for break; case NEW: case RUNNABLE: case BLOCKED: case WAITING: case TIMED_WAITING: default: serverSocketThread.interrupt(); break; } serverSocketThread = null; } /** * Nested Class to handle the ServerSocket.accept requests */ class ServerSocketThread extends Thread { ServerSocket serverSocket; ServerSocketThread(ServerSocket serverSocket) { this.setName(createThreadName(serverSocket)); this.serverSocket = serverSocket; } /** * Derive a thread name from the class name, the component URI and the connection information. * <p/> * The String will in the format <class name>[endpoint key] - [local socket address] * * @return String for thread name */ String createThreadName(ServerSocket serverSocket) { // Get the classname without the package. This is a nested class, so we want the parent class name included String fullClassName = this.getClass().getName(); String className = fullClassName.substring(fullClassName.lastIndexOf('.') + 1); // Get the URI without options String fullEndpointKey = endpoint.getEndpointKey(); String endpointKey; if (fullEndpointKey.contains("?")) { endpointKey = fullEndpointKey.substring(0, fullEndpointKey.indexOf('?')); } else { endpointKey = fullEndpointKey; } // Now put it all together return String.format("%s[%s] - %s", className, endpointKey, serverSocket.getLocalSocketAddress()); } /** * The main ServerSocket.accept() loop * <p/> * NOTE: When a connection is received, the Socket is checked after a brief delay in an attempt to determine * if this is a load-balancer probe. The test is done before the ClientSocketThread is created to avoid creating * a large number of short lived threads, which is what can occur if the load balancer polling interval is very * short. */ public void run() { MDC.put("camel.contextId", endpoint.getCamelContext().getName()); try { while (!isInterrupted() && null != serverSocket && serverSocket.isBound() && !serverSocket.isClosed()) { Socket socket = null; try { socket = serverSocket.accept(); } catch (SocketTimeoutException timeoutEx) { // Didn't get a new connection - keep waiting for one log.debug("Timeout waiting for client connection - keep listening"); continue; } catch (SocketException socketEx) { // This should happen if the component is closed while the accept call is blocking if (serverSocket.isBound()) { try { serverSocket.close(); } catch (Exception ex) { log.debug("Exception encountered closing ServerSocket after SocketException on accept() - ignoring", ex); } } continue; } catch (IOException ioEx) { log.error("Exception encountered accepting connection - closing ServerSocket", ioEx); if (serverSocket.isBound()) { try { serverSocket.close(); } catch (Exception ex) { log.debug("Exception encountered closing ServerSocket after exception on accept() - ignoring", ex); } } continue; } try { /* Wait a bit and then check and see if the socket is really there - it could be a load balancer pinging the port */ if (socket.isConnected() && !socket.isClosed()) { log.debug("Socket appears to be there - checking for available data in {} milliseconds", SOCKET_STARTUP_TEST_WAIT); Thread.sleep(SOCKET_STARTUP_TEST_WAIT); InputStream inputStream; try { inputStream = socket.getInputStream(); } catch (IOException ioEx) { MllpSocketUtil.reset(socket, log, "Failed to retrieve the InputStream for socket after the initial connection was accepted"); continue; } if (0 < inputStream.available()) { // Something is there - start the client thread ClientSocketThread clientThread = new ClientSocketThread(socket, null); clientThreads.add(clientThread); clientThread.start(); continue; } // The easy check failed - so trigger a blocking read MllpSocketUtil.setSoTimeout(socket, SOCKET_STARTUP_TEST_READ_TIMEOUT, log, "Preparing to check for available data on component startup"); try { int tmpByte = inputStream.read(); if (-1 == tmpByte) { log.debug("Check for available data failed - Socket.read() returned END_OF_STREAM"); MllpSocketUtil.close(socket, null, null); } else { ClientSocketThread clientThread = new ClientSocketThread(socket, tmpByte); clientThreads.add(clientThread); clientThread.start(); } } catch (SocketTimeoutException timeoutEx) { // No data, but the socket is there String logMessageFormat = "Check for available data failed - Socket.read() timed-out after {} milliseconds." + " No Data - but the socket is there. Starting ClientSocketThread"; log.debug(logMessageFormat, SOCKET_STARTUP_TEST_READ_TIMEOUT); ClientSocketThread clientThread = new ClientSocketThread(socket, null); clientThreads.add(clientThread); clientThread.start(); } catch (IOException ioEx) { log.debug("Ignoring IOException encountered when attempting to read a byte - connection was reset"); try { socket.close(); } catch (IOException closeEx) { log.debug("Ignoring IOException encountered when attempting to close the connection after the connection reset was detected", closeEx); } } } } catch (SocketTimeoutException timeoutEx) { // No new clients log.trace("SocketTimeoutException waiting for new connections - no new connections"); for (int i = clientThreads.size() - 1; i >= 0; --i) { ClientSocketThread thread = clientThreads.get(i); if (!thread.isAlive()) { clientThreads.remove(i); } } } catch (InterruptedException interruptEx) { log.debug("accept loop interrupted - closing ServerSocket"); try { serverSocket.close(); } catch (Exception ex) { log.debug("Exception encountered closing ServerSocket after InterruptedException - ignoring", ex); } } catch (Exception ex) { log.error("Exception accepting new connection - retrying", ex); } } } finally { log.debug("ServerSocket.accept loop finished - closing listener"); if (null != serverSocket && serverSocket.isBound() && !serverSocket.isClosed()) { try { serverSocket.close(); } catch (Exception ex) { log.debug("Exception encountered closing ServerSocket after accept loop had exited - ignoring", ex); } } } } @Override public void interrupt() { super.interrupt(); if (null != serverSocket) { if (serverSocket.isBound()) { try { serverSocket.close(); } catch (IOException ioEx) { log.warn("Exception encountered closing ServerSocket in interrupt() method - ignoring", ioEx); } } } } } /** * Nested Class reads the Socket */ class ClientSocketThread extends Thread { final Socket clientSocket; final MllpSocketReader mllpSocketReader; final MllpSocketWriter mllpSocketWriter; Integer initialByte; ClientSocketThread(Socket clientSocket, Integer initialByte) throws IOException { this.initialByte = initialByte; this.setName(createThreadName(clientSocket)); this.clientSocket = clientSocket; this.clientSocket.setKeepAlive(endpoint.keepAlive); this.clientSocket.setTcpNoDelay(endpoint.tcpNoDelay); if (null != endpoint.receiveBufferSize) { this.clientSocket.setReceiveBufferSize(endpoint.receiveBufferSize); } if (null != endpoint.sendBufferSize) { this.clientSocket.setSendBufferSize(endpoint.sendBufferSize); } this.clientSocket.setReuseAddress(endpoint.reuseAddress); this.clientSocket.setSoLinger(false, -1); // Initial Read Timeout MllpSocketUtil.setSoTimeout(clientSocket, endpoint.receiveTimeout, log, "Constructing ClientSocketThread"); mllpSocketReader = new MllpSocketReader(this.clientSocket, endpoint.receiveTimeout, endpoint.readTimeout, false); if (endpoint.bufferWrites) { mllpSocketWriter = new MllpBufferedSocketWriter(this.clientSocket, true); } else { mllpSocketWriter = new MllpSocketWriter(this.clientSocket, true); } } /** * derive a thread name from the class name, the component URI and the connection information * <p/> * The String will in the format <class name>[endpoint key] - [local socket address] -> [remote socket address] * * @return the thread name */ String createThreadName(Socket socket) { // Get the classname without the package. This is a nested class, so we want the parent class name included String fullClassName = this.getClass().getName(); String className = fullClassName.substring(fullClassName.lastIndexOf('.') + 1); // Get the URI without options String fullEndpointKey = endpoint.getEndpointKey(); String endpointKey; if (fullEndpointKey.contains("?")) { endpointKey = fullEndpointKey.substring(0, fullEndpointKey.indexOf('?')); } else { endpointKey = fullEndpointKey; } // Now put it all together return String.format("%s[%s] - %s -> %s", className, endpointKey, socket.getLocalSocketAddress(), socket.getRemoteSocketAddress()); } @Override public void run() { int receiveTimeoutCounter = 0; MDC.put("camel.contextId", endpoint.getCamelContext().getName()); while (!isInterrupted() && null != clientSocket && clientSocket.isConnected() && !clientSocket.isClosed()) { byte[] hl7MessageBytes = null; log.debug("Checking for data ...."); try { hl7MessageBytes = mllpSocketReader.readEnvelopedPayload(initialByte); if (hl7MessageBytes == null) { // No data received - check for max timeouts if (endpoint.maxReceiveTimeouts > 0 && ++receiveTimeoutCounter >= endpoint.maxReceiveTimeouts) { String reasonMessage = String.format("Idle Client after %d receive timeouts [%d-milliseconds] - resetting connection", receiveTimeoutCounter, endpoint.receiveTimeout); MllpSocketUtil.reset(clientSocket, log, reasonMessage); } continue; } } catch (MllpException mllpEx) { Exchange exchange = endpoint.createExchange(ExchangePattern.InOut); exchange.setException(mllpEx); log.warn("Exception encountered reading payload - sending exception to route", mllpEx); try { getProcessor().process(exchange); } catch (Exception e) { log.error("Exception encountered processing exchange with exception encounter reading payload", e); } continue; } finally { initialByte = null; } // Send the message on for processing and wait for the response log.debug("Populating the exchange with received message"); Exchange exchange = endpoint.createExchange(ExchangePattern.InOut); try { createUoW(exchange); Message message = exchange.getIn(); message.setBody(hl7MessageBytes, byte[].class); message.setHeader(MLLP_LOCAL_ADDRESS, clientSocket.getLocalAddress().toString()); message.setHeader(MLLP_REMOTE_ADDRESS, clientSocket.getRemoteSocketAddress()); message.setHeader(MLLP_AUTO_ACKNOWLEDGE, endpoint.autoAck); if (endpoint.validatePayload) { String exceptionMessage = Hl7Util.generateInvalidPayloadExceptionMessage(hl7MessageBytes); if (exceptionMessage != null) { exchange.setException(new MllpInvalidMessageException(exceptionMessage, hl7MessageBytes)); } } populateHl7DataHeaders(exchange, message, hl7MessageBytes); log.debug("Calling processor"); try { getProcessor().process(exchange); sendAcknowledgement(hl7MessageBytes, exchange); } catch (RuntimeException runtimeEx) { throw runtimeEx; } catch (Exception ex) { log.error("Unexpected exception processing exchange", ex); } } catch (Exception uowEx) { // TODO: Handle this correctly exchange.setException(uowEx); log.warn("Exception encountered creating Unit of Work - sending exception to route", uowEx); try { getProcessor().process(exchange); } catch (Exception e) { log.error("Exception encountered processing exchange with exception encountered createing Unit of Work", e); } continue; } finally { if (exchange != null) { doneUoW(exchange); } } } log.debug("ClientSocketThread exiting"); } private void sendAcknowledgement(byte[] originalHl7MessageBytes, Exchange exchange) { log.info("sendAcknowledgement"); // Check BEFORE_SEND Properties if (exchange.getProperty(MLLP_RESET_CONNECTION_BEFORE_SEND, boolean.class)) { String reasonMessage = String.format("Exchange property %s is %b", MLLP_RESET_CONNECTION_BEFORE_SEND, exchange.getProperty(MLLP_RESET_CONNECTION_BEFORE_SEND, boolean.class)); MllpSocketUtil.reset(clientSocket, log, reasonMessage); return; } else if (exchange.getProperty(MLLP_CLOSE_CONNECTION_BEFORE_SEND, boolean.class)) { String reasonMessage = String.format("Exchange property %s is %b", MLLP_CLOSE_CONNECTION_BEFORE_SEND, exchange.getProperty(MLLP_CLOSE_CONNECTION_BEFORE_SEND, boolean.class)); MllpSocketUtil.close(clientSocket, log, reasonMessage); return; } // Find the acknowledgement body // TODO: Enhance this to say whether or not the acknowledgment is missing or just of an un-convertible type byte[] acknowledgementMessageBytes = exchange.getProperty(MLLP_ACKNOWLEDGEMENT, byte[].class); String acknowledgementMessageType = null; if (null == acknowledgementMessageBytes) { boolean autoAck = exchange.getProperty(MLLP_AUTO_ACKNOWLEDGE, true, boolean.class); if (!autoAck) { exchange.setException(new MllpInvalidAcknowledgementException("Automatic Acknowledgement is disabled and the " + MLLP_ACKNOWLEDGEMENT + " exchange property is null or cannot be converted to byte[]", originalHl7MessageBytes, acknowledgementMessageBytes)); return; } String acknowledgmentTypeProperty = exchange.getProperty(MLLP_ACKNOWLEDGEMENT_TYPE, String.class); try { if (null == acknowledgmentTypeProperty) { if (null == exchange.getException()) { acknowledgementMessageType = "AA"; acknowledgementMessageBytes = acknowledgementGenerator.generateApplicationAcceptAcknowledgementMessage(originalHl7MessageBytes); } else { acknowledgementMessageType = "AE"; acknowledgementMessageBytes = acknowledgementGenerator.generateApplicationErrorAcknowledgementMessage(originalHl7MessageBytes); } } else { switch (acknowledgmentTypeProperty) { case "AA": acknowledgementMessageType = "AA"; acknowledgementMessageBytes = acknowledgementGenerator.generateApplicationAcceptAcknowledgementMessage(originalHl7MessageBytes); break; case "AE": acknowledgementMessageType = "AE"; acknowledgementMessageBytes = acknowledgementGenerator.generateApplicationErrorAcknowledgementMessage(originalHl7MessageBytes); break; case "AR": acknowledgementMessageType = "AR"; acknowledgementMessageBytes = acknowledgementGenerator.generateApplicationRejectAcknowledgementMessage(originalHl7MessageBytes); break; default: exchange.setException(new Hl7AcknowledgementGenerationException("Unsupported acknowledgment type: " + acknowledgmentTypeProperty)); return; } } } catch (Hl7AcknowledgementGenerationException ackGenerationException) { exchange.setProperty(MLLP_ACKNOWLEDGEMENT_EXCEPTION, ackGenerationException); exchange.setException(ackGenerationException); } } else { final byte bM = 77; final byte bS = 83; final byte bA = 65; final byte bE = 69; final byte bR = 82; final byte fieldSeparator = originalHl7MessageBytes[3]; // Acknowledgment is specified in exchange property - determine the acknowledgement type for (int i = 0; i < originalHl7MessageBytes.length; ++i) { if (SEGMENT_DELIMITER == i) { if (i + 7 < originalHl7MessageBytes.length // Make sure we don't run off the end of the message && bM == originalHl7MessageBytes[i + 1] && bS == originalHl7MessageBytes[i + 2] && bA == originalHl7MessageBytes[i + 3] && fieldSeparator == originalHl7MessageBytes[i + 4]) { if (fieldSeparator != originalHl7MessageBytes[i + 7]) { log.warn("MSA-1 is longer than 2-bytes - ignoring trailing bytes"); } // Found MSA - pull acknowledgement bytes byte[] acknowledgmentTypeBytes = new byte[2]; acknowledgmentTypeBytes[0] = originalHl7MessageBytes[i + 5]; acknowledgmentTypeBytes[1] = originalHl7MessageBytes[i + 6]; try { acknowledgementMessageType = IOConverter.toString(acknowledgmentTypeBytes, exchange); } catch (IOException ioEx) { throw new RuntimeException("Failed to convert acknowledgement message to string", ioEx); } // Verify it's a valid acknowledgement code if (bA != acknowledgmentTypeBytes[0]) { switch (acknowledgementMessageBytes[1]) { case bA: case bR: case bE: break; default: log.warn("Invalid acknowledgement type [" + acknowledgementMessageType + "] found in message - should be AA, AE or AR"); } } // if the MLLP_ACKNOWLEDGEMENT_TYPE property is set on the exchange, make sure it matches String acknowledgementTypeProperty = exchange.getProperty(MLLP_ACKNOWLEDGEMENT_TYPE, String.class); if (null != acknowledgementTypeProperty && !acknowledgementTypeProperty.equals(acknowledgementMessageType)) { log.warn("Acknowledgement type found in message [" + acknowledgementMessageType + "] does not match " + MLLP_ACKNOWLEDGEMENT_TYPE + " exchange property value [" + acknowledgementTypeProperty + "] - using value found in message"); } } } } } Message message; if (exchange.hasOut()) { message = exchange.getOut(); } else { message = exchange.getIn(); } message.setHeader(MLLP_ACKNOWLEDGEMENT, acknowledgementMessageBytes); // TODO: Use the charset of the exchange message.setHeader(MLLP_ACKNOWLEDGEMENT_STRING, new String(acknowledgementMessageBytes)); message.setHeader(MLLP_ACKNOWLEDGEMENT_TYPE, acknowledgementMessageType); // Send the acknowledgement log.debug("Sending Acknowledgement: {}", MllpComponent.covertBytesToPrintFriendlyString(acknowledgementMessageBytes)); try { mllpSocketWriter.writeEnvelopedPayload(originalHl7MessageBytes, acknowledgementMessageBytes); } catch (MllpException mllpEx) { log.error("MLLP Acknowledgement failure: {}", mllpEx); MllpAcknowledgementDeliveryException deliveryException = new MllpAcknowledgementDeliveryException(originalHl7MessageBytes, acknowledgementMessageBytes, mllpEx); exchange.setProperty(MLLP_ACKNOWLEDGEMENT_EXCEPTION, deliveryException); exchange.setException(deliveryException); } // Check AFTER_SEND Properties if (exchange.getProperty(MLLP_RESET_CONNECTION_AFTER_SEND, boolean.class)) { String reasonMessage = String.format("Exchange property %s is %b", MLLP_RESET_CONNECTION_AFTER_SEND, exchange.getProperty(MLLP_RESET_CONNECTION_AFTER_SEND, boolean.class)); MllpSocketUtil.reset(clientSocket, log, reasonMessage); return; } else if (exchange.getProperty(MLLP_CLOSE_CONNECTION_AFTER_SEND, boolean.class)) { String reasonMessage = String.format("Exchange property %s is %b", MLLP_CLOSE_CONNECTION_AFTER_SEND, exchange.getProperty(MLLP_CLOSE_CONNECTION_AFTER_SEND, boolean.class)); MllpSocketUtil.reset(clientSocket, log, reasonMessage); } } private void populateHl7DataHeaders(Exchange exchange, Message message, byte[] hl7MessageBytes) { if (hl7MessageBytes == null || hl7MessageBytes.length < 8) { // Not enough data to populate anything - just return return; } // Find the end of the MSH and indexes of the fields in the MSH to populate message headers final byte fieldSeparator = hl7MessageBytes[3]; int endOfMSH = -1; List<Integer> fieldSeparatorIndexes = new ArrayList<>(10); // We should have at least 10 fields for (int i = 0; i < hl7MessageBytes.length; ++i) { if (fieldSeparator == hl7MessageBytes[i]) { fieldSeparatorIndexes.add(i); } else if (SEGMENT_DELIMITER == hl7MessageBytes[i]) { // If the MSH Segment doesn't have a trailing field separator, add one so the field can be extracted into a header if (fieldSeparator != hl7MessageBytes[i - 1]) { fieldSeparatorIndexes.add(i); } endOfMSH = i; break; } } String messageBodyForDebugging = new String(hl7MessageBytes); if (-1 == endOfMSH) { // TODO: May want to throw some sort of an Exception here log.error("Population of message headers failed - unable to find the end of the MSH segment"); } else if (endpoint.hl7Headers) { log.debug("Populating the HL7 message headers"); Charset charset = Charset.forName(IOHelper.getCharsetName(exchange)); for (int i = 2; i < fieldSeparatorIndexes.size(); ++i) { int startingFieldSeparatorIndex = fieldSeparatorIndexes.get(i - 1); int endingFieldSeparatorIndex = fieldSeparatorIndexes.get(i); // Only populate the header if there's data in the HL7 field if (endingFieldSeparatorIndex - startingFieldSeparatorIndex > 1) { String headerName = null; switch (i) { case 2: // MSH-3 headerName = MLLP_SENDING_APPLICATION; break; case 3: // MSH-4 headerName = MLLP_SENDING_FACILITY; break; case 4: // MSH-5 headerName = MLLP_RECEIVING_APPLICATION; break; case 5: // MSH-6 headerName = MLLP_RECEIVING_FACILITY; break; case 6: // MSH-7 headerName = MLLP_TIMESTAMP; break; case 7: // MSH-8 headerName = MLLP_SECURITY; break; case 8: // MSH-9 headerName = MLLP_MESSAGE_TYPE; break; case 9: // MSH-10 headerName = MLLP_MESSAGE_CONTROL; break; case 10: // MSH-11 headerName = MLLP_PROCESSING_ID; break; case 11: // MSH-12 headerName = MLLP_VERSION_ID; break; case 17: // MSH-18 headerName = MLLP_CHARSET; break; default: // Not processing this field continue; } String headerValue = new String(hl7MessageBytes, startingFieldSeparatorIndex + 1, endingFieldSeparatorIndex - startingFieldSeparatorIndex - 1, charset); message.setHeader(headerName, headerValue); // For MSH-9, set a couple more headers if (i == 8) { // final byte componentSeparator = hl7MessageBytes[4]; String componentSeparator = new String(hl7MessageBytes, 4, 1, charset); String[] components = headerValue.split(String.format("\\Q%s\\E", componentSeparator), 3); message.setHeader(MLLP_EVENT_TYPE, components[0]); if (2 <= components.length) { message.setHeader(MLLP_TRIGGER_EVENT, components[1]); } } } } } else { log.trace("HL7 Message headers disabled"); } } @Override public void interrupt() { if (null != clientSocket && clientSocket.isConnected() && !clientSocket.isClosed()) { MllpSocketUtil.close(clientSocket, log, this.getClass().getSimpleName() + " interrupted"); } super.interrupt(); } } }