All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.camel.component.mllp.internal.MllpSocketBuffer Maven / Gradle / Ivy

The newest version!
/*
 * 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.internal;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.util.Arrays;

import org.apache.camel.component.mllp.MllpComponent;
import org.apache.camel.component.mllp.MllpEndpoint;
import org.apache.camel.component.mllp.MllpProtocolConstants;
import org.apache.camel.component.mllp.MllpSocketException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An OutputStream modeled after the ByteArrayOutputStream specifically for MLLP operations.
 */
public class MllpSocketBuffer {

    private static final Logger LOG = LoggerFactory.getLogger(MllpSocketBuffer.class);
    final MllpEndpoint endpoint;

    byte[] buffer;
    int availableByteCount;

    int startOfBlockIndex = -1;
    int endOfBlockIndex = -1;
    String charset;
    Hl7Util hl7Util;
    int minBufferSize;
    int maxBufferSize;

    public MllpSocketBuffer(MllpEndpoint endpoint) {
        if (endpoint == null) {
            throw new IllegalArgumentException("MllpEndpoint cannot be null");
        }
        this.endpoint = endpoint;
        this.charset = endpoint.getCharsetName();
        MllpComponent component = endpoint.getComponent();
        this.hl7Util = new Hl7Util(component.getLogPhiMaxBytes(), component.getLogPhi());
        this.minBufferSize = endpoint.getConfiguration().getMinBufferSize();
        this.maxBufferSize = endpoint.getConfiguration().getMaxBufferSize();

        buffer = new byte[minBufferSize];
    }

    public boolean isEndOfDataRequired() {
        return endpoint.getConfiguration().isRequireEndOfData();
    }

    public boolean isEmpty() {
        return size() <= 0;
    }

    public synchronized void write(int b) {
        ensureCapacity(1);
        buffer[availableByteCount] = (byte) b;

        updateIndexes(b, 0);

        availableByteCount += 1;
    }

    public void write(byte[] b) {
        if (b != null && b.length > 0) {
            this.write(b, 0, b.length);
        }
    }

    public synchronized void write(byte[] sourceBytes, int offset, int writeCount) {
        if (sourceBytes != null && sourceBytes.length > 0) {
            if (offset < 0) {
                throw new IndexOutOfBoundsException(
                        String.format("write(byte[%d], offset[%d], writeCount[%d]) - offset is less than zero",
                                sourceBytes.length, offset, writeCount));
            }
            if (offset > sourceBytes.length) {
                throw new IndexOutOfBoundsException(
                        String.format("write(byte[%d], offset[%d], writeCount[%d]) - offset is greater than write count",
                                sourceBytes.length, offset, writeCount));
            }

            if (writeCount < 0) {
                throw new IndexOutOfBoundsException(
                        String.format("write(byte[%d], offset[%d], writeCount[%d]) - write count is less than zero",
                                sourceBytes.length, offset, writeCount));
            }
            if (writeCount > sourceBytes.length) {
                throw new IndexOutOfBoundsException(
                        String.format(
                                "write(byte[%d], offset[%d], writeCount[%d]) - write count is greater than length of the source byte[]",
                                sourceBytes.length, offset, writeCount));
            }
            if ((offset + writeCount) - sourceBytes.length > 0) {
                throw new IndexOutOfBoundsException(
                        String.format(
                                "write(byte[%d], offset[%d], writeCount[%d]) - offset plus write count <%d> is greater than length of the source byte[]",
                                sourceBytes.length, offset, writeCount, offset + writeCount));
            }

            ensureCapacity(writeCount);
            System.arraycopy(sourceBytes, offset, buffer, availableByteCount, writeCount);

            for (int i = offset; i < writeCount && (startOfBlockIndex < 0 || endOfBlockIndex < 0); ++i) {
                updateIndexes(sourceBytes[i], i);
            }

            availableByteCount += writeCount;
        }
    }

    public synchronized void openMllpEnvelope() {
        reset();
        write(MllpProtocolConstants.START_OF_BLOCK);
    }

    public synchronized void closeMllpEnvelope() {
        write(MllpProtocolConstants.PAYLOAD_TERMINATOR);
    }

    public synchronized void setEnvelopedMessage(byte[] hl7Payload) {
        setEnvelopedMessage(hl7Payload, 0, hl7Payload != null ? hl7Payload.length : 0);
    }

    public synchronized void setEnvelopedMessage(byte[] hl7Payload, int offset, int length) {
        reset();

        if (hl7Payload != null && hl7Payload.length > 0) {
            if (hl7Payload[0] != MllpProtocolConstants.START_OF_BLOCK) {
                openMllpEnvelope();
            }

            write(hl7Payload, offset, length);

            if (!hasCompleteEnvelope()) {
                closeMllpEnvelope();
            }
        } else {
            openMllpEnvelope();
            closeMllpEnvelope();
        }
    }

    public synchronized void reset() {
        if (availableByteCount > 0) {
            // TODO: May be able to get rid of this
            Arrays.fill(buffer, (byte) 0);
        }

        availableByteCount = 0;

        startOfBlockIndex = -1;
        endOfBlockIndex = -1;
    }

    public synchronized void readFrom(Socket socket) throws MllpSocketException, SocketTimeoutException {
        readFrom(socket, endpoint.getConfiguration().getReceiveTimeout(), endpoint.getConfiguration().getReadTimeout());
    }

    public synchronized void readFrom(Socket socket, int receiveTimeout, int readTimeout)
            throws MllpSocketException, SocketTimeoutException {
        if (socket != null && socket.isConnected() && !socket.isClosed()) {
            LOG.trace("readFrom({}, {}, {}) - entering", socket, receiveTimeout, readTimeout);
            ensureCapacity(minBufferSize);

            try {
                InputStream socketInputStream = socket.getInputStream();

                socket.setSoTimeout(receiveTimeout);

                readSocketInputStream(socketInputStream, socket);
                if (!hasCompleteEnvelope()) {
                    socket.setSoTimeout(readTimeout);

                    while (!hasCompleteEnvelope()) {
                        ensureCapacity(Math.max(minBufferSize, socketInputStream.available()));
                        readSocketInputStream(socketInputStream, socket);
                    }
                }

            } catch (SocketTimeoutException timeoutEx) {
                throw timeoutEx;
            } catch (IOException ioEx) {
                final String exceptionMessage
                        = String.format("readFrom(%s, %d, %d) - IOException encountered", socket, receiveTimeout, readTimeout);
                resetSocket(socket, exceptionMessage);
                throw new MllpSocketException(exceptionMessage, ioEx);
            } finally {
                if (size() > 0 && !hasCompleteEnvelope()) {
                    if (!hasEndOfData() && hasEndOfBlock() && endOfBlockIndex < size() - 1) {
                        LOG.warn("readFrom({}, {}, {}) - exiting with partial payload {}", socket, receiveTimeout, readTimeout,
                                hl7Util.convertToPrintFriendlyString(buffer, 0, size() - 1));
                    }
                }
            }

        } else {
            LOG.warn("readFrom({}, {}, {}) - no data read because Socket is invalid", socket, receiveTimeout, readTimeout);
        }

        LOG.trace("readFrom({}, {}, {}) - exiting", socket, receiveTimeout, readTimeout);
    }

    public synchronized void writeTo(Socket socket) throws MllpSocketException {
        if (socket != null && socket.isConnected() && !socket.isClosed()) {
            LOG.trace("writeTo({}) - entering", socket);
            if (!isEmpty()) {
                try {
                    OutputStream socketOutputStream = socket.getOutputStream();
                    if (hasStartOfBlock()) {
                        if (hasEndOfData()) {
                            socketOutputStream.write(buffer, startOfBlockIndex, endOfBlockIndex - startOfBlockIndex + 2);
                        } else if (hasEndOfBlock()) {
                            socketOutputStream.write(buffer, startOfBlockIndex, endOfBlockIndex - startOfBlockIndex + 1);
                            socketOutputStream.write(MllpProtocolConstants.END_OF_DATA);
                        } else {
                            socketOutputStream.write(buffer, startOfBlockIndex, availableByteCount - startOfBlockIndex);
                            socketOutputStream.write(MllpProtocolConstants.PAYLOAD_TERMINATOR);
                        }
                    } else {
                        socketOutputStream.write(MllpProtocolConstants.START_OF_BLOCK);
                        socketOutputStream.write(buffer, 0, availableByteCount);
                        socketOutputStream.write(MllpProtocolConstants.PAYLOAD_TERMINATOR);
                    }
                    socketOutputStream.flush();
                } catch (IOException ioEx) {
                    final String exceptionMessage = String.format("writeTo(%s) - IOException encountered", socket);
                    resetSocket(socket, exceptionMessage);
                    throw new MllpSocketException(exceptionMessage, ioEx);
                }
            } else {
                LOG.warn("writeTo({}) - no data written because buffer is empty", socket);
            }
        } else {
            LOG.warn("writeTo({}) - no data written because Socket is invalid", socket);
        }

        LOG.trace("writeTo({}) - exiting", socket);
    }

    public synchronized byte[] toByteArray() {
        if (availableByteCount > 0) {
            return Arrays.copyOf(buffer, availableByteCount);
        }

        return null;
    }

    public synchronized byte[] toByteArrayAndReset() {
        byte[] answer = toByteArray();

        reset();

        return answer;
    }

    @Override
    public synchronized String toString() {
        if (charset != null) {
            return toString(charset);
        } else {
            return toString(endpoint.getComponent().getDefaultCharset());
        }
    }

    public synchronized String toString(Charset charset) {
        if (availableByteCount > 0) {
            return new String(buffer, 0, availableByteCount, charset);
        }

        return "";
    }

    public synchronized String toString(String charsetName) {
        if (availableByteCount > 0) {
            try {
                if (Charset.isSupported(charsetName)) {
                    return toString(Charset.forName(charsetName));
                }
            } catch (Exception charsetEx) {
                // ignore
            }
        }

        return "";
    }

    /**
     * Convert the entire contents of the buffer (including enveloping characters) to a print-friendly String
     * representation.
     *
     * @return print-friendly String
     */
    public synchronized String toPrintFriendlyString() {
        if (availableByteCount > 0) {
            return hl7Util.convertToPrintFriendlyString(buffer, 0, availableByteCount);
        }

        return "";
    }

    public String toPrintFriendlyStringAndReset() {
        String answer = toPrintFriendlyString();

        reset();

        return answer;
    }

    public synchronized String toHl7String() {
        return this.toHl7String(charset);
    }

    public synchronized String toHl7String(String charsetName) {
        if (charsetName != null && !charsetName.isEmpty()) {
            try {
                if (Charset.isSupported(charsetName)) {
                    return toHl7String(Charset.forName(charsetName));
                }
            } catch (Exception charsetEx) {
                // ignore
            }
        }

        if (Charset.isSupported(endpoint.getComponent().getDefaultCharset())) {
            return toHl7String(endpoint.getComponent().getDefaultCharset());
        }

        return "";
    }

    public synchronized String toHl7String(Charset charset) {
        if (hasCompleteEnvelope()) {
            int offset = hasStartOfBlock() ? startOfBlockIndex + 1 : 1;
            int length = hasEndOfBlock() ? endOfBlockIndex - offset : availableByteCount - startOfBlockIndex - 1;
            if (length > 0) {
                return new String(buffer, offset, length, charset);
            } else {
                return "";
            }
        }

        return null;
    }

    /**
     * Convert the enveloped contents of the buffer (excluding enveloping characters) to a print-friendly String
     * representation.
     *
     * @return print-friendly String
     */
    public synchronized String toPrintFriendlyHl7String() {
        if (hasCompleteEnvelope()) {
            int startPosition = hasStartOfBlock() ? startOfBlockIndex + 1 : 1;
            int endPosition = hasEndOfBlock() ? endOfBlockIndex : availableByteCount - 1;
            return hl7Util.convertToPrintFriendlyString(buffer, startPosition, endPosition);
        }

        return "";
    }

    public synchronized byte[] toMllpPayload() {
        byte[] mllpPayload = null;

        if (hasCompleteEnvelope()) {
            int offset = hasStartOfBlock() ? startOfBlockIndex + 1 : 1;
            int length = hasEndOfBlock() ? endOfBlockIndex - offset : availableByteCount - startOfBlockIndex - 1;

            if (length > 0) {
                mllpPayload = new byte[length];
                System.arraycopy(buffer, offset, mllpPayload, 0, length);
            } else {
                mllpPayload = new byte[0];
            }
        }

        return mllpPayload;
    }

    public synchronized int getStartOfBlockIndex() {
        return startOfBlockIndex;
    }

    public synchronized int getEndOfBlockIndex() {
        return endOfBlockIndex;
    }

    public synchronized boolean hasCompleteEnvelope() {
        if (hasStartOfBlock()) {
            if (isEndOfDataRequired()) {
                return hasEndOfData();
            } else {
                return hasEndOfBlock();
            }
        }

        return false;
    }

    public synchronized boolean hasStartOfBlock() {
        return startOfBlockIndex >= 0;
    }

    public synchronized boolean hasEndOfBlock() {
        return endOfBlockIndex >= 0;
    }

    public synchronized boolean hasEndOfData() {
        if (hasEndOfBlock()) {
            int potentialEndOfDataIndex = endOfBlockIndex + 1;
            if (potentialEndOfDataIndex < availableByteCount
                    && buffer[potentialEndOfDataIndex] == MllpProtocolConstants.END_OF_DATA) {
                return true;
            }
        }

        return false;
    }

    public synchronized boolean hasOutOfBandData() {
        return hasLeadingOutOfBandData() || hasTrailingOutOfBandData();
    }

    public synchronized boolean hasLeadingOutOfBandData() {
        if (size() > 0) {
            if (!hasStartOfBlock() || startOfBlockIndex > 0) {
                return true;
            }
        }

        return false;
    }

    public synchronized boolean hasTrailingOutOfBandData() {
        if (size() > 0) {
            if (hasEndOfData()) {
                if (endOfBlockIndex + 1 < size() - 1) {
                    return true;
                }
            } else if (!isEndOfDataRequired()) {
                if (hasEndOfBlock() && endOfBlockIndex < size() - 1) {
                    return true;
                }
            }
        }

        return false;
    }

    public synchronized byte[] getLeadingOutOfBandData() {
        byte[] outOfBandData = null;

        if (hasLeadingOutOfBandData()) {
            outOfBandData = new byte[startOfBlockIndex == -1 ? availableByteCount : startOfBlockIndex];
            System.arraycopy(buffer, 0, outOfBandData, 0, outOfBandData.length);
        }

        return outOfBandData;
    }

    public synchronized byte[] getTrailingOutOfBandData() {
        byte[] outOfBandData = null;

        if (hasTrailingOutOfBandData()) {
            int offset = hasEndOfData() ? endOfBlockIndex + 2 : endOfBlockIndex + 1;
            int length = size() - offset;
            outOfBandData = new byte[length];
            System.arraycopy(buffer, offset, outOfBandData, 0, length);
        }

        return outOfBandData;
    }

    public synchronized int size() {
        return availableByteCount;
    }

    public synchronized int capacity() {
        if (buffer != null) {
            return buffer.length - availableByteCount;
        }

        return -1;
    }

    void ensureCapacity(int requiredAvailableCapacity) {
        int currentAvailableCapacity = capacity();

        if (requiredAvailableCapacity > currentAvailableCapacity) {
            int requiredBufferSize = buffer.length + (requiredAvailableCapacity - currentAvailableCapacity);

            if (buffer.length >= maxBufferSize) {
                final String exceptionMessageFormat = "Cannot increase the buffer size from <%d> to <%d>"
                                                      + " in order to increase the available capacity from <%d> to <%d> because the buffer is already the maximum size <%d>";
                throw new IllegalStateException(
                        String.format(exceptionMessageFormat, buffer.length, requiredBufferSize, currentAvailableCapacity,
                                requiredAvailableCapacity, maxBufferSize));
            } else if (requiredBufferSize > maxBufferSize) {
                final String exceptionMessageFormat = "Cannot increase the buffer size <%d>"
                                                      + " in order to increase the available capacity from <%d> to <%d> because the required buffer size <%d> exceeds the maximum buffer size <%d>";
                throw new IllegalStateException(
                        String.format(exceptionMessageFormat, buffer.length, currentAvailableCapacity,
                                requiredAvailableCapacity, requiredBufferSize, maxBufferSize));
            }
            int newBufferSize = Math.min(buffer.length + Math.max(minBufferSize, requiredAvailableCapacity), maxBufferSize);

            buffer = Arrays.copyOf(buffer, newBufferSize);
        }
    }

    void updateIndexes(int b, int indexOffset) {
        if (startOfBlockIndex < 0) {
            if (b == MllpProtocolConstants.START_OF_BLOCK) {
                startOfBlockIndex = availableByteCount + indexOffset;
            }
        } else if (endOfBlockIndex < 0 && b == MllpProtocolConstants.END_OF_BLOCK) {
            endOfBlockIndex = availableByteCount + indexOffset;
        }
    }

    void readSocketInputStream(InputStream socketInputStream, Socket socket)
            throws MllpSocketException, SocketTimeoutException {
        LOG.trace("readSocketInputStream(socketInputStream, {}) - entering with initial buffer size = {}", socket, size());
        try {
            int readCount = socketInputStream.read(buffer, availableByteCount, buffer.length - availableByteCount);
            if (readCount == MllpProtocolConstants.END_OF_STREAM) {
                final String exceptionMessage = String.format(
                        "readSocketInputStream(socketInputStream, %s) - END_OF_STREAM returned from SocketInputStream.read(byte[%d], %d, %d)",
                        socket, buffer.length, availableByteCount, buffer.length - availableByteCount);
                resetSocket(socket);
                throw new MllpSocketException(exceptionMessage);
            }
            if (readCount > 0) {
                for (int i = 0; (startOfBlockIndex == -1 || endOfBlockIndex == -1) && i < readCount; ++i) {
                    updateIndexes(buffer[availableByteCount + i], i);
                }
                availableByteCount += readCount;

                if (hasStartOfBlock()) {
                    LOG.trace("readSocketInputStream(socketInputStream, {}) - read {} bytes for a total of {} bytes", socket,
                            readCount, availableByteCount);
                } else {
                    LOG.warn(
                            "readSocketInputStream(socketInputStream, {}) - ignoring {} bytes received before START_OF_BLOCK: {}",
                            socket, size(), toPrintFriendlyStringAndReset());
                }
            }
        } catch (SocketTimeoutException timeoutEx) {
            throw timeoutEx;
        } catch (IOException ioEx) {
            final String exceptionMessage = String.format(
                    "readSocketInputStream(socketInputStream, %s) - IOException thrown from SocketInputStream.read(byte[%d], %d, %d) from %s",
                    socket, buffer.length, availableByteCount, buffer.length - availableByteCount, socket);
            resetSocket(socket);
            throw new MllpSocketException(exceptionMessage, ioEx);
        } finally {
            LOG.trace("readSocketInputStream(socketInputStream, {}) - exiting with buffer size = {}", socket, size());
        }
    }

    public void closeSocket(Socket socket) {
        doSocketClose(socket, null, false);
    }

    public void closeSocket(Socket socket, String logMessage) {
        doSocketClose(socket, logMessage, false);
    }

    public void resetSocket(Socket socket) {
        doSocketClose(socket, null, true);
    }

    public void resetSocket(Socket socket, String logMessage) {
        doSocketClose(socket, logMessage, true);
    }

    void doSocketClose(Socket socket, String logMessage, boolean reset) {
        if (socket != null && socket.isConnected() && !socket.isClosed()) {
            if (logMessage != null && !logMessage.isEmpty()) {
                LOG.info("{} - {} socket {}", reset ? "Resetting" : "Closing", logMessage, socket);
            } else {
                LOG.debug("{} socket {}", reset ? "Resetting" : "Closing", socket);
            }

            endpoint.updateLastConnectionTerminatedTicks();

            if (!socket.isInputShutdown()) {
                try {
                    socket.shutdownInput();
                } catch (IOException ignoredEx) {
                    LOG.trace(
                            "doSocketClose(socket[{}], logMessage[{}], reset[{}] - ignoring exception raised by Socket.shutdownInput()",
                            socket, logMessage, reset, ignoredEx);
                }
            }

            if (!socket.isOutputShutdown()) {
                try {
                    socket.shutdownOutput();
                } catch (IOException ignoredEx) {
                    LOG.trace(
                            "doSocketClose(socket[{}], logMessage[{}], reset[{}] - ignoring exception raised by Socket.shutdownOutput()",
                            socket, logMessage, reset, ignoredEx);
                }
            }

            if (reset) {
                final boolean on = true;
                final int linger = 0;
                try {
                    socket.setSoLinger(on, linger);
                } catch (IOException ignoredEx) {
                    LOG.trace(
                            "doSocketClose(socket[{}], logMessage[{}], reset[{}] - ignoring exception raised by Socket.setSoLinger({}, {})",
                            socket, logMessage, reset, on, linger, ignoredEx);
                }
            }

            try {
                socket.close();
            } catch (IOException ignoredEx) {
                // TODO: Maybe log this
                LOG.trace("doSocketClose(socket[{}], logMessage[{}], reset[{}] - ignoring exception raised by Socket.close()",
                        socket, logMessage, reset, ignoredEx);
            }
        }
    }

    public static boolean isConnectionValid(Socket socket) {
        return socket != null && socket.isConnected() && !socket.isClosed();
    }

    public static String formatAddressString(SocketAddress sourceAddress, SocketAddress targetAddress) {
        String sourceAddressString = null;
        String targetAddressString = null;

        if (sourceAddress != null) {
            sourceAddressString = sourceAddress.toString();
        }

        if (targetAddress != null) {
            targetAddressString = targetAddress.toString();
        }

        return String.format("%s => %s", sourceAddressString, targetAddressString);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy