/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2006-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.netmgt.eventd.adaptors.tcp; import java.io.BufferedInputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.io.PipedOutputStream; import java.net.Socket; import java.util.List; import org.opennms.core.utils.InetAddressUtils; import org.opennms.core.utils.LogUtils; /** * This class is used to do the initial read of data from the input stream and * break it up into records. Each record is written to a piped writer. This * means that the reader never gets too far ahead of the parse. It means more * threads for less memory usage. As always there is a tradeoff. * * @author <a href="mailto:weave@oculan.com">Brian Weaver </a> * @author <a href="http;//www.opennms.org">OpenNMS </a> * */ final class TcpRecordHandler implements Runnable { /** * When set the runnable should exit as fast as possible. */ private volatile boolean m_stop; /** * The thread context running this runnable */ private Thread m_context; /** * The list of piped output streams and execptions. */ private List<Object> m_xchange; /** * The input stream socket */ private Socket m_connection; /** * The current pipe. */ private OutputStream m_out; /** * The set of state managers */ private StateManager[] m_tokenizer; /** * This class is use to model the set of states, the attached * TcpRecordHandler, and the transition actions. */ private static class StateManager { /** * The level of this manager */ protected int m_level; /** * The record handler this manager is attached to */ protected TcpRecordHandler m_handler; /** * Constructs a new state manager. * * @param level * The level of the state manager * @param handler * The handler to fire events at. */ StateManager(final int level, final TcpRecordHandler handler) { m_level = level; m_handler = handler; } /** * The level for this manager */ @SuppressWarnings("unused") int getLevel() { return m_level; } /** * handle the next character, returns the next level */ int next(final char ch) throws IOException { onTransition(ch); return m_level; } /** * Handle the transition from character to character. */ void onTransition(final char ch) throws IOException { m_handler.forward(ch); } } /** * Closes the current stream if any */ private void closeStream() throws IOException { // close the current output stream if (m_out != null) { m_out.close(); } m_out = null; } /** * Allocates a new stream */ private void newStream() throws IOException { LogUtils.debugf(this, "Opening new PipedOutputStream and adding it to the queue"); // create a new piped writer final PipedOutputStream pipeOut = new PipedOutputStream(); try { synchronized (pipeOut) { synchronized (m_xchange) { m_xchange.add(pipeOut); m_xchange.notify(); } LogUtils.debugf(this, "Added pipe to the xchange list"); pipeOut.wait(); LogUtils.debugf(this, "Pipe Signaled"); } } catch (final InterruptedException e) { LogUtils.debugf(this, e, "An I/O error occured."); throw new IOException("The thread was interrupted"); } LogUtils.debugf(this, "PipedOutputStream connected"); m_out = pipeOut; } /** * forwards the characters to the attached pipe. */ private void forward(final char ch) throws IOException { try { if (m_out != null) { m_out.write((int) ch); } } catch (final IOException e) { LogUtils.debugf(this, e, "An I/O error occured."); throw e; } } /** * Constructs a new record handler. * * @param s * The socket to read from * @param xchange * The io exchange */ TcpRecordHandler(final Socket s, final List<Object> xchange) { m_stop = false; m_context = null; m_xchange = xchange; m_connection = s; // looks for '</([a-zA-Z0-9]+:)?log>' m_tokenizer = new StateManager[] { new StateManager(0, this) { int next(final char ch) throws IOException { onTransition(ch); if (ch == '<') { return 1; } return m_level; } }, new StateManager(1, this) { int next(final char ch) throws IOException { onTransition(ch); if (ch == '/') { return 2; } return 0; } }, new StateManager(2, this) { int next(final char ch) throws IOException { onTransition(ch); if (ch == 'l') { return 5; } else if (Character.isLetterOrDigit(ch)){ return 3; } return 0; } }, new StateManager(3, this) { int next(final char ch) throws IOException { onTransition(ch); if (ch == ':') { return 4; } else if (Character.isLetterOrDigit(ch)) { return m_level; } return 0; } }, new StateManager(4, this) { int next(final char ch) throws IOException { onTransition(ch); if (ch == 'l') { return 5; } return 0; } }, new StateManager(5, this) { int next(final char ch) throws IOException { onTransition(ch); if (ch == 'o') { return 6; } return 0; } }, new StateManager(6, this) { int next(final char ch) throws IOException { onTransition(ch); if (ch == 'g') { return 7; } return 0; } }, new StateManager(7, this) { int next(final char ch) throws IOException { onTransition(ch); if (ch == '>') { m_handler.closeStream(); return 8; } return 0; } }, // The state tree starts here! new StateManager(8, this) { // gobbles up white space after // record int next(final char ch) throws IOException { if (ch == '<') { onTransition(ch); return 1; } // else discard return m_level; } void onTransition(final char ch) throws IOException { m_handler.newStream(); super.onTransition(ch); } } }; } /** * Returns true if the context is alive */ boolean isAlive() { if (m_context != null) { return m_context.isAlive(); } else { return false; } } /** * Stops and joins the current context. */ void stop() throws InterruptedException { m_stop = true; if (m_context != null) { LogUtils.debugf(this, "Interrupting thread %s", m_context.getName()); m_context.interrupt(); LogUtils.debugf(this, "Joining Thread %s", m_context.getName()); m_context.join(); LogUtils.debugf(this, "Thread %s Joined", m_context.getName()); } } /** * The execution context. */ public void run() { // get the thread context right off m_context = Thread.currentThread(); synchronized (m_context) { m_context.notifyAll(); } /* * Check the stop flag, if it is set then go a head and exit * before doing any work on the socket */ if (m_stop) { LogUtils.debugf(this, "Stop flag set before thread startup, thread exiting"); return; } else { LogUtils.debugf(this, "Thread started, remote is %s", InetAddressUtils.str(m_connection.getInetAddress())); } // get the input stream InputStream socketIn = null; try { m_connection.setSoTimeout(500); // needed in case connection closed! socketIn = new BufferedInputStream(m_connection.getInputStream()); } catch (final IOException e) { if (!m_stop) { LogUtils.warnf(this, e, "An I/O Exception occured."); } m_xchange.add(e); LogUtils.debugf(this, "Thread exiting due to socket exception, stop flag = %s", Boolean.valueOf(m_stop)); return; } int level = 8; int ch = 0; boolean moreInput = true; while (moreInput) { // check to see if the thread is interrupted if (Thread.interrupted()) { LogUtils.debugf(this, "Thread Interrupted"); break; } try { ch = socketIn.read(); if (ch == -1) { moreInput = false; continue; } } catch (final InterruptedIOException e) { // this was expected continue; } catch (final EOFException e) { m_xchange.add(e); moreInput = false; continue; } catch (final IOException e) { m_xchange.add(e); if (!m_stop) { LogUtils.warnf(this, e, "An I/O error occured reading from the remote host."); } moreInput = false; continue; } try { level = m_tokenizer[level].next((char) ch); } catch (final IOException e) { if (!m_stop) { LogUtils.warnf(this, e, "An I/O error occured writing to the processor stream."); LogUtils.warnf(this, "Discarding the remainder of the event contents"); try { /* * this will discard current stream * and cause all forwards to be discarded. */ closeStream(); } catch (final IOException e2) { } } else { m_xchange.add(e); moreInput = false; } } } // ensure that the receiver knows that no new element is coming! try { if (m_out != null) { m_out.close(); } } catch (final IOException e) { if (!m_stop) { LogUtils.warnf(this, e, "An I/O Error occured closing the processor stream."); } } m_xchange.add(new EOFException("No More Input")); LogUtils.debugf(this, "Thread Terminated"); } }