/**
* 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;
}
}