/* * Copyright 2003,2004,2005 Colin Crist * * Licensed 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 hermes.fix.quickfix; import hermes.HermesRuntimeException; import hermes.fix.FIXException; import hermes.fix.FIXMessage; import hermes.fix.FIXMessageFilter; import hermes.fix.FIXReader; import hermes.fix.MalformedMessageException; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Method; import java.nio.BufferUnderflowException; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import org.apache.log4j.Logger; /** * @author colincrist@hermesjms.com * @version $Id: NIOFIXFileReader.java,v 1.5 2006/08/25 11:33:48 colincrist Exp $ */ public class NIOFIXFileReader implements FIXReader, Runnable { private static final Logger log = Logger.getLogger(NIOFIXFileReader.class); private static byte[] startOfMessage = new byte[] { '8', '=', 'F', 'I', 'X' }; private FileInputStream istream; private MappedByteBuffer parseBuffer; private MappedByteBuffer readBuffer; private Object lock = new Object(); private int position = 0; private int mappedStart; private BlockingQueue<FIXMessage> messages = new ArrayBlockingQueue<FIXMessage>(8192); private QuickFIXMessageCache messageCache; private FIXMessageFilter filter = new FIXMessageFilter(); public FIXMessageFilter getFilter() { return filter; } public NIOFIXFileReader(QuickFIXMessageCache messageCache, FileInputStream istream) throws IOException { this.istream = istream; this.messageCache = messageCache; map(); new Thread(this).start(); } public FIXMessage read() throws IOException { return read(-1); } public FIXMessage read(final long timeout) { try { FIXMessage rval = messages.poll(100, TimeUnit.MILLISECONDS); while (rval == null && istream.getChannel().isOpen()) { rval = messages.poll(100, TimeUnit.MILLISECONDS); } return rval; } catch (Exception ex) { log.error(ex.getMessage(), ex); return null; } } public byte[] getBytes(int offset, int length) { synchronized (lock) { byte[] bytes = new byte[length]; readBuffer.position(offset); readBuffer.get(bytes); return bytes; } } private void waitAndRemap() throws InterruptedException, IOException { Thread.sleep(500); map(); } public void run() { try { while (istream.getChannel().isOpen()) { try { final FIXMessage m = readMessage(); try { if (m != null && filter.filter(m.getMsgType())) { messages.put(m); } } catch (HermesRuntimeException ex) { log.error("ignoring invalid message: " + ex.getMessage(), ex); } } catch (BufferUnderflowException ex) { waitAndRemap(); } catch (IllegalArgumentException ex) { waitAndRemap(); } } log.debug("channel closed"); } catch (Throwable ex) { log.error(ex.getMessage(), ex); } } protected FIXMessage readMessage() throws MalformedMessageException, FIXException, BufferUnderflowException, InterruptedException, IOException { parseBuffer.position(position); byte b; int startOfMessageIndex = 0; while (startOfMessageIndex < startOfMessage.length) { b = parseBuffer.get(); if (startOfMessage[startOfMessageIndex] == b) { startOfMessageIndex++; } else { startOfMessageIndex = 0; } } int startOfMessageOffset = parseBuffer.position() - startOfMessage.length; // // Found a message, scan for the next tag. byte[] protocolAsBytes = new byte[12]; int protocolAsBytesIndex = 0; while ((b = parseBuffer.get()) != '\1') { protocolAsBytes[protocolAsBytesIndex++] = b; } protocolAsBytes[protocolAsBytesIndex++] = '\0'; String protocol = "FIX" + new String(protocolAsBytes).trim(); b = parseBuffer.get(); if (b != '9') { position = parseBuffer.position(); throw new MalformedMessageException("Tag 9 does not follow tag 8"); } parseBuffer.get(); byte[] messageLengthBuffer = new byte[16]; int messageLengthBufferOffset = 0; while ((b = parseBuffer.get()) != '\1') { messageLengthBuffer[messageLengthBufferOffset++] = b; } messageLengthBuffer[messageLengthBufferOffset++] = '\1'; final String s = new String(messageLengthBuffer).trim(); final int fixLength = Integer.parseInt(s); parseBuffer.position(parseBuffer.position() + fixLength); // Scan over the last tag while ((b = parseBuffer.get()) != '\1') { } final int messageLength = parseBuffer.position() - startOfMessageOffset; position = parseBuffer.position(); protocolAsBytes[protocolAsBytesIndex++] = '\0'; return new NIOQuickFIXMessage(messageCache, this, mappedStart + startOfMessageOffset, messageLength, QuickFIXUtils.getDictionary(protocol)); } private void map() throws IOException { synchronized (lock) { FileChannel channel = istream.getChannel(); if (channel.isOpen()) { if (parseBuffer == null || channel.size() > mappedStart) { if (parseBuffer != null) { clean(parseBuffer); } if (readBuffer != null) { clean(readBuffer); } // log.debug("mapping in FIX file, mappedStart=" + mappedStart + // " channel.size()=" + channel.size()); mappedStart += position; parseBuffer = channel.map(FileChannel.MapMode.READ_ONLY, mappedStart, channel.size() - mappedStart); readBuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); position = 0; } } } } private final void clean(final MappedByteBuffer buffer) { AccessController.doPrivileged(new PrivilegedAction() { public Object run() { try { Method getCleanerMethod = buffer.getClass().getMethod("cleaner", new Class[0]); getCleanerMethod.setAccessible(true); sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(buffer, new Object[0]); cleaner.clean(); } catch (Exception e) { log.error(e.getMessage(), e); } return null; } }); } public void release() { synchronized (lock) { if (readBuffer != null) { log.debug("releasing read memory map"); clean(readBuffer); readBuffer = null; } } } @Override protected void finalize() throws Throwable { release(); } public void close() { synchronized (lock) { try { if (istream != null) { istream.getChannel().close(); istream.close(); if (parseBuffer != null) { log.debug("releasing parse memory map"); clean(parseBuffer); } parseBuffer = null; } } catch (IOException ex) { throw new HermesRuntimeException(ex); } } } }