Java tutorial
/* * Copyright (c) Mirth Corporation. All rights reserved. * * http://www.mirthcorp.com * * The software in this package is published under the terms of the MPL license a copy of which has * been included with this distribution in the LICENSE.txt file. */ package com.mirth.connect.model.transmission.framemode; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.SocketException; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.ArrayUtils; import org.apache.log4j.Logger; import com.mirth.connect.donkey.server.message.StreamHandler; import com.mirth.connect.donkey.server.message.batch.BatchStreamReader; import com.mirth.connect.model.transmission.TransmissionModeProperties; import com.mirth.connect.util.TcpUtil; public class FrameStreamHandler extends StreamHandler { private Logger logger = Logger.getLogger(this.getClass()); protected byte[] startOfMessageBytes; protected byte[] endOfMessageBytes; protected boolean returnDataOnException; // Determines whether data should be returned if an exception occurs. private ByteArrayOutputStream capturedBytes; // The bytes captured so far by the reader, not including any in the end bytes buffer. private List<Byte> endBytesBuffer; // An interim buffer of bytes used to capture the ending byte sequence. private byte lastByte; // The last byte returned from getNextByte. private boolean streamDone; // This is true if an EOF has been read in, or if the ending byte sequence has been detected. private boolean checkStartOfMessageBytes; private int currentByte; public FrameStreamHandler(InputStream inputStream, OutputStream outputStream, BatchStreamReader batchStreamReader, TransmissionModeProperties transmissionModeProperties) { super(inputStream, outputStream, batchStreamReader); FrameModeProperties frameModeProperties = (FrameModeProperties) transmissionModeProperties; this.startOfMessageBytes = TcpUtil.stringToByteArray(frameModeProperties.getStartOfMessageBytes()); this.endOfMessageBytes = TcpUtil.stringToByteArray(frameModeProperties.getEndOfMessageBytes()); // Only return data on exceptions if there are no end bytes defined this.returnDataOnException = endOfMessageBytes.length == 0; this.checkStartOfMessageBytes = true; this.streamDone = false; } public byte[] getStartOfMessageBytes() { return startOfMessageBytes; } public void setStartOfMessageBytes(byte[] startOfMessageBytes) { this.startOfMessageBytes = startOfMessageBytes; } public byte[] getEndOfMessageBytes() { return endOfMessageBytes; } public void setEndOfMessageBytes(byte[] endOfMessageBytes) { this.endOfMessageBytes = endOfMessageBytes; } public boolean isReturnDataOnException() { return returnDataOnException; } public void setReturnDataOnException(boolean returnDataOnException) { this.returnDataOnException = returnDataOnException; } @Override public void setInputStream(InputStream inputStream) { this.inputStream = inputStream; batchStreamReader.setInputStream(inputStream); } public void reset() { checkStartOfMessageBytes = true; streamDone = false; } /** * Returns the next message from the stream (could be the entire stream contents or part of a * batch). * * @return A byte array representing the next whole message in the stream (could be the entire * stream contents or part of a batch), or null if the stream is done. If an IOException * is caught while reading (e.g. a socket timeout) and returnDataOnException is true, * then all bytes accumulated up to that point are returned. * @throws IOException * If an IOException is caught while reading (e.g. a socket timeout) and * returnDataOnException is false. */ @Override public byte[] read() throws IOException { if (streamDone || inputStream == null) { return null; } capturedBytes = new ByteArrayOutputStream(); List<Byte> firstBytes = new ArrayList<Byte>(); // A List is used here to allow the buffer to simulate a "shifting window" of potential bytes. endBytesBuffer = new ArrayList<Byte>(); try { // Skip to the beginning of the message if (checkStartOfMessageBytes) { int i = 0; while (i < startOfMessageBytes.length) { currentByte = inputStream.read(); logger.trace("Checking for start of message bytes, currentByte: " + currentByte); if (currentByte != -1) { if (firstBytes.size() < startOfMessageBytes.length) { firstBytes.add((byte) currentByte); } if (currentByte == (int) (startOfMessageBytes[i] & 0xFF)) { i++; } else { i = 0; } } else { streamDone = true; if (firstBytes.size() > 0) { throw new FrameStreamHandlerException(true, startOfMessageBytes, ArrayUtils.toPrimitive(firstBytes.toArray(new Byte[0]))); } else { // The input stream ended before the begin bytes were detected, so return null return null; } } } // Begin bytes were found checkStartOfMessageBytes = false; } // Allow the handler to initialize anything it needs to (e.g. mark the input stream) batchStreamReader.initialize(); // Iterate while there are still bytes to read, or if we're checking for end bytes and its buffer is not empty while ((currentByte = batchStreamReader.getNextByte()) != -1 || (endOfMessageBytes.length > 0 && !endBytesBuffer.isEmpty())) { // If the input stream is done, get the byte from the buffer instead if (currentByte == -1) { currentByte = endBytesBuffer.remove(0); streamDone = true; } else { lastByte = (byte) currentByte; } // Check to see if an end frame has been received if (endOfMessageBytes.length > 0 && !streamDone) { if (endBytesBuffer.size() == endOfMessageBytes.length) { // Shift the buffer window over one, popping the first element and writing it to the output stream capturedBytes.write(endBytesBuffer.remove(0)); } // Add the byte to the buffer endBytesBuffer.add((byte) currentByte); // Check to see if the current buffer window equals the ending byte sequence boolean endBytesFound = true; for (int i = 0; i <= endBytesBuffer.size() - 1; i++) { if (endBytesBuffer.get(i) != endOfMessageBytes[i]) { endBytesFound = false; break; } } if (endBytesFound) { // Ending bytes sequence has been detected streamDone = true; return capturedBytes.toByteArray(); } } else { // Add the byte to the main output stream capturedBytes.write(currentByte); } if (!streamDone) { // Allow subclass to check the current byte stream and return immediately byte[] returnBytes = batchStreamReader.checkForIntermediateMessage(capturedBytes, endBytesBuffer, lastByte); if (returnBytes != null) { return returnBytes; } } } } catch (Throwable e) { if (!returnDataOnException) { if (e instanceof IOException) { // If an IOException occurred and we're not allowing data to return, throw the exception if (checkStartOfMessageBytes && firstBytes.size() > 0) { // At least some bytes have been read, but the start of message bytes were not detected throw new FrameStreamHandlerException(true, startOfMessageBytes, ArrayUtils.toPrimitive(firstBytes.toArray(new Byte[0])), e); } if (capturedBytes.size() + endBytesBuffer.size() > 0 && endOfMessageBytes.length > 0) { // At least some bytes have been captured, but the end of message bytes were not detected throw new FrameStreamHandlerException(false, endOfMessageBytes, getLastBytes(), e); } throw (IOException) e; } else { // If any other Throwable was caught, return null to indicate that we're done return null; } } } if (endOfMessageBytes.length > 0) { // If we got here, then the end of message bytes were not captured throw new FrameStreamHandlerException(false, endOfMessageBytes, getLastBytes()); } else { /* * If we got here, no end of message bytes were expected, but we should reset the check * flag so that the next time a read is performed, it will attempt to capture the * starting bytes again. */ checkStartOfMessageBytes = true; } // Flush the buffer to the main output stream for (Byte bufByte : endBytesBuffer) { capturedBytes.write(bufByte); } return capturedBytes.size() > 0 ? capturedBytes.toByteArray() : null; } @Override public void write(byte[] data) throws IOException { writeFrame(data); } protected void writeFrame(byte[] data) throws IOException { write(startOfMessageBytes, data, endOfMessageBytes); } protected void write(byte[]... dataArrays) throws IOException { if (dataArrays == null || outputStream == null) { return; } DataOutputStream dos = new DataOutputStream(outputStream); for (byte[] data : dataArrays) { if (data != null) { for (byte b : data) { dos.writeByte(b); } } } try { dos.flush(); } catch (SocketException e) { logger.debug("Socket closed while trying to flush."); } } private byte[] getLastBytes() { int capturedBytesLength = capturedBytes != null ? capturedBytes.size() : 0; int endBytesBufferLength = endBytesBuffer != null ? endBytesBuffer.size() : 0; // If the total bytes read is less than the number of expected end bytes, use the smaller value byte[] lastBytes = new byte[Math.min(capturedBytesLength + endBytesBufferLength, endOfMessageBytes.length)]; int index = 0; // Add any captured bytes, leaving room for the end bytes buffer if (capturedBytes != null) { byte[] capturedByteArray = capturedBytes.toByteArray(); for (int i = capturedBytesLength - lastBytes.length + endBytesBufferLength; i >= 0 && i < capturedBytesLength; i++) { lastBytes[index++] = capturedByteArray[i]; } } // Fill the remainder of the array with the end bytes buffer if (endBytesBuffer != null) { for (byte b : endBytesBuffer) { lastBytes[index++] = b; } } return lastBytes; } }