/** * 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.net.InetSocketAddress; import java.net.Socket; import java.net.SocketException; import org.apache.camel.Exchange; import org.apache.camel.Message; 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.impl.DefaultProducer; import org.apache.camel.util.IOHelper; import static org.apache.camel.component.mllp.MllpConstants.MLLP_ACKNOWLEDGEMENT; 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_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_LOCAL_ADDRESS; 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.MllpEndpoint.SEGMENT_DELIMITER; /** * The MLLP producer. */ @ManagedResource(description = "MllpTcpClient Producer") public class MllpTcpClientProducer extends DefaultProducer { MllpEndpoint endpoint; Socket socket; MllpSocketReader mllpSocketReader; MllpSocketWriter mllpSocketWriter; public MllpTcpClientProducer(MllpEndpoint endpoint) throws SocketException { super(endpoint); log.trace("MllpTcpClientProducer(endpoint)"); this.endpoint = endpoint; } @Override protected void doStart() throws Exception { log.trace("doStart()"); super.doStart(); } @Override protected void doStop() throws Exception { log.trace("doStop()"); MllpSocketUtil.close(socket, log, "Stopping component"); super.doStop(); } @ManagedOperation(description = "Close client socket") public void closeMllpSocket() { MllpSocketUtil.close(socket, log, "JMX triggered closing socket"); } @ManagedOperation(description = "Reset client socket") public void resetMllpSocket() { MllpSocketUtil.reset(socket, log, "JMX triggered resetting socket"); } @Override public void process(Exchange exchange) throws Exception { log.trace("process(exchange)"); // Check BEFORE_SEND Properties if (exchange.getProperty(MLLP_RESET_CONNECTION_BEFORE_SEND, boolean.class)) { MllpSocketUtil.reset(socket, log, "Exchange property " + MLLP_RESET_CONNECTION_BEFORE_SEND + " = " + exchange.getProperty(MLLP_RESET_CONNECTION_BEFORE_SEND, boolean.class)); return; } else if (exchange.getProperty(MLLP_CLOSE_CONNECTION_BEFORE_SEND, boolean.class)) { MllpSocketUtil.close(socket, log, "Exchange property " + MLLP_CLOSE_CONNECTION_BEFORE_SEND + " = " + exchange.getProperty(MLLP_CLOSE_CONNECTION_BEFORE_SEND, boolean.class)); return; } // Establish a connection if needed try { checkConnection(); } catch (IOException ioEx) { exchange.setException(ioEx); return; } Message message; if (exchange.hasOut()) { message = exchange.getOut(); } else { message = exchange.getIn(); } message.setHeader(MLLP_LOCAL_ADDRESS, socket.getLocalAddress().toString()); message.setHeader(MLLP_REMOTE_ADDRESS, socket.getRemoteSocketAddress().toString()); // Send the message to the external system byte[] hl7MessageBytes = message.getMandatoryBody(byte[].class); byte[] acknowledgementBytes = null; try { log.debug("Sending message to external system"); mllpSocketWriter.writeEnvelopedPayload(hl7MessageBytes, null); log.debug("Reading acknowledgement from external system"); acknowledgementBytes = mllpSocketReader.readEnvelopedPayload(hl7MessageBytes); } catch (MllpWriteException writeEx) { MllpSocketUtil.reset(socket, log, writeEx.getMessage()); exchange.setException(writeEx); return; } catch (MllpReceiveException ackReceiveEx) { MllpSocketUtil.reset(socket, log, ackReceiveEx.getMessage()); exchange.setException(ackReceiveEx); return; } catch (MllpException mllpEx) { Throwable mllpExCause = mllpEx.getCause(); if (mllpExCause != null && mllpExCause instanceof IOException) { MllpSocketUtil.reset(socket, log, mllpEx.getMessage()); } exchange.setException(mllpEx); return; } log.debug("Populating message headers with the acknowledgement from the external system"); message.setHeader(MLLP_ACKNOWLEDGEMENT, acknowledgementBytes); message.setHeader(MLLP_ACKNOWLEDGEMENT_STRING, new String(acknowledgementBytes, IOHelper.getCharsetName(exchange, true))); if (endpoint.validatePayload) { String exceptionMessage = Hl7Util.generateInvalidPayloadExceptionMessage(acknowledgementBytes); if (exceptionMessage != null) { exchange.setException(new MllpInvalidAcknowledgementException(exceptionMessage, hl7MessageBytes, acknowledgementBytes)); return; } } log.debug("Processing the acknowledgement from the external system"); try { String acknowledgementType = processAcknowledgment(hl7MessageBytes, acknowledgementBytes); message.setHeader(MLLP_ACKNOWLEDGEMENT_TYPE, acknowledgementType); } catch (MllpException mllpEx) { exchange.setException(mllpEx); return; } // Check AFTER_SEND Properties if (exchange.getProperty(MLLP_RESET_CONNECTION_AFTER_SEND, boolean.class)) { MllpSocketUtil.reset(socket, log, "Exchange property " + MLLP_RESET_CONNECTION_AFTER_SEND + " = " + exchange.getProperty(MLLP_RESET_CONNECTION_AFTER_SEND, boolean.class)); } else if (exchange.getProperty(MLLP_CLOSE_CONNECTION_AFTER_SEND, boolean.class)) { MllpSocketUtil.close(socket, log, "Exchange property " + MLLP_CLOSE_CONNECTION_AFTER_SEND + " = " + exchange.getProperty(MLLP_CLOSE_CONNECTION_AFTER_SEND, boolean.class)); } } private String processAcknowledgment(byte[] hl7MessageBytes, byte[] hl7AcknowledgementBytes) throws MllpException { String acknowledgementType = ""; if (hl7AcknowledgementBytes != null && hl7AcknowledgementBytes.length > 3) { // Extract the acknowledgement type and check for a NACK byte fieldDelim = hl7AcknowledgementBytes[3]; // First, find the beginning of the MSA segment - should be the second segment int msaStartIndex = -1; for (int i = 0; i < hl7AcknowledgementBytes.length; ++i) { if (SEGMENT_DELIMITER == hl7AcknowledgementBytes[i]) { final byte bM = 77; final byte bS = 83; final byte bC = 67; final byte bA = 65; final byte bE = 69; final byte bR = 82; /* We've found the start of a new segment - make sure peeking ahead won't run off the end of the array - we need at least 7 more bytes */ if (hl7AcknowledgementBytes.length > i + 7) { // We can safely peek ahead if (bM == hl7AcknowledgementBytes[i + 1] && bS == hl7AcknowledgementBytes[i + 2] && bA == hl7AcknowledgementBytes[i + 3] && fieldDelim == hl7AcknowledgementBytes[i + 4]) { // Found the beginning of the MSA - the next two bytes should be our acknowledgement code msaStartIndex = i + 1; if (bA != hl7AcknowledgementBytes[i + 5] && bC != hl7AcknowledgementBytes[i + 5]) { String errorMessage = "Unsupported acknowledgement type: " + new String(hl7AcknowledgementBytes, i + 5, 2); throw new MllpInvalidAcknowledgementException(errorMessage, hl7MessageBytes, hl7AcknowledgementBytes); } else { switch (hl7AcknowledgementBytes[i + 6]) { case bA: // We have an AA or CA if (bA == hl7AcknowledgementBytes[i + 5]) { acknowledgementType = "AA"; } else { acknowledgementType = "CA"; } break; case bE: // We have an AE or CE if (bA == hl7AcknowledgementBytes[i + 5]) { throw new MllpApplicationErrorAcknowledgementException(hl7MessageBytes, hl7AcknowledgementBytes); } else { throw new MllpCommitErrorAcknowledgementException(hl7MessageBytes, hl7AcknowledgementBytes); } case bR: // We have an AR or CR if (bA == hl7AcknowledgementBytes[i + 5]) { throw new MllpApplicationRejectAcknowledgementException(hl7MessageBytes, hl7AcknowledgementBytes); } else { throw new MllpCommitRejectAcknowledgementException(hl7MessageBytes, hl7AcknowledgementBytes); } default: String errorMessage = "Unsupported acknowledgement type: " + new String(hl7AcknowledgementBytes, i + 5, 2); throw new MllpInvalidAcknowledgementException(errorMessage, hl7MessageBytes, hl7AcknowledgementBytes); } } break; } } } } if (-1 == msaStartIndex && endpoint.validatePayload) { // Didn't find an MSA throw new MllpInvalidAcknowledgementException("MSA Not found in acknowledgement", hl7MessageBytes, hl7AcknowledgementBytes); } } return acknowledgementType; } /** * Validate the TCP Connection, if closed opens up the socket with * the value set via endpoint configuration * * @throws IOException if the connection is not valid, otherwise the Exception is not * encountered while checking the connection */ private void checkConnection() throws IOException { if (null == socket || socket.isClosed() || !socket.isConnected()) { socket = new Socket(); socket.setKeepAlive(endpoint.keepAlive); socket.setTcpNoDelay(endpoint.tcpNoDelay); if (null != endpoint.receiveBufferSize) { socket.setReceiveBufferSize(endpoint.receiveBufferSize); } else { endpoint.receiveBufferSize = socket.getReceiveBufferSize(); } if (null != endpoint.sendBufferSize) { socket.setSendBufferSize(endpoint.sendBufferSize); } else { endpoint.sendBufferSize = socket.getSendBufferSize(); } socket.setReuseAddress(endpoint.reuseAddress); socket.setSoLinger(false, -1); InetSocketAddress socketAddress; if (null == endpoint.getHostname()) { socketAddress = new InetSocketAddress(endpoint.getPort()); } else { socketAddress = new InetSocketAddress(endpoint.getHostname(), endpoint.getPort()); } log.debug("Connecting to socket on {}", socketAddress); socket.connect(socketAddress, endpoint.connectTimeout); log.debug("Creating MllpSocketReader and MllpSocketWriter"); mllpSocketReader = new MllpSocketReader(socket, endpoint.receiveTimeout, endpoint.readTimeout, true); if (endpoint.bufferWrites) { mllpSocketWriter = new MllpBufferedSocketWriter(socket, false); } else { mllpSocketWriter = new MllpSocketWriter(socket, false); } } return; } @ManagedOperation(description = "Check client connection") public boolean managedCheckConnection() { boolean isValid = true; try { checkConnection(); } catch (IOException ioEx) { isValid = false; log.debug("JMX check connection: {}", ioEx); } return isValid; } }