/*******************************************************************************
* 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.IOException;
import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.opennms.core.fiber.Fiber;
import org.opennms.core.utils.InetAddressUtils;
import org.opennms.core.utils.ThreadCategory;
import org.opennms.netmgt.eventd.adaptors.EventHandler;
/**
* This class implement the server features necessary to receive events from
* incoming connections.
*
* @author <a href="mailto:weave@oculan.com">Brian Weaver </a>
* @author <a href="http;//www.opennms.org">OpenNMS </a>
*
*/
final class TcpServer implements Runnable {
/**
* The default TCP/IP port where the server listens for connections. Each
* connection to the server will be processed by its own thread.
*/
static final int TCP_PORT = 5817;
/**
* The default IP address where the server listens for connections.
*/
static final String DEFAULT_IP_ADDRESS = "127.0.0.1";
/**
* The TCP/IP Port for the server socket's binding. By default this should
* be equal to {@link #TCP_PORT TCP_PORT}but it can be overridden in the
* constructor.
*/
private int m_tcpPort;
/**
* The TCP/IP Port for the server socket's binding. By default this should
* be equal to {@link #TCP_PORT TCP_PORT}but it can be overridden in the
* constructor.
*/
private ServerSocket m_tcpSock;
/**
* When set true the server thread will exit.
*/
private volatile boolean m_stop;
/**
* <p>
* The list of receivers that are currently being processed. Each instance
* in this list is of type {@link java.lang.Thread Thread}and will remain
* in the list so long as it's alive.
* </p>
*
* <p>
* This list is periodically cleaned by the main server thread.
* </p>
*/
private LinkedList<TcpStreamHandler> m_receivers;
/**
* The thread which is executing the server context
*/
private Thread m_context;
/**
* The parent fiber.
*/
private Fiber m_parent;
/**
* The list of event handlers
*/
private List<EventHandler> m_handlers;
/**
* The logging context
*/
private String m_logPrefix;
/**
* the events per connection
*/
private int m_recsPerConn;
private InetAddress m_ipAddress;
/**
* Constructs a new instance of an server to handle incoming tcp
* connections.
*
* @param parent
* The parent fiber
* @param handlers a {@link java.util.List} object.
* @throws java.io.IOException if any.
*/
public TcpServer(Fiber parent, List<EventHandler> handlers) throws IOException {
this(parent, handlers, TCP_PORT, InetAddressUtils.addr(DEFAULT_IP_ADDRESS));
}
/**
* Constructs a new instance of an server to handle incoming TCP
* connections.
*
* @param parent
* The parent fiber
* @param port
* The port to listen on.
* @param address TODO
* @param handlers a {@link java.util.List} object.
* @throws java.io.IOException if any.
*/
public TcpServer(Fiber parent, List<EventHandler> handlers, int port, InetAddress address) throws IOException {
m_parent = parent;
m_tcpPort = port;
m_ipAddress = address;
m_receivers = new LinkedList<TcpStreamHandler>();
m_stop = false;
m_context = null;
m_handlers = handlers;
m_logPrefix = org.opennms.netmgt.eventd.Eventd.LOG4J_CATEGORY;
m_recsPerConn = TcpEventReceiver.UNLIMITED_EVENTS;
try {
m_tcpSock = new ServerSocket(m_tcpPort, 0, m_ipAddress);
} catch (IOException e) {
IOException n = new IOException("Could not create listening TCP socket on " + m_ipAddress + ":" + m_tcpPort + ": " + e);
n.initCause(e);
throw n;
}
}
/**
* This is called inform the current execution of this object is stopped.
* Once called the object cannot be reused in another thread.
*
* @throws java.lang.InterruptedException if any.
*/
public void stop() throws InterruptedException {
log().debug("stop method invoked");
// Stop this context
m_stop = true;
if (m_context != null) {
if (log().isDebugEnabled()) {
log().debug("Interrupting and joining context thread " + m_context.getName());
}
m_context.interrupt();
m_context.join();
if (log().isDebugEnabled()) {
log().debug("Thread context stopped and joined " + m_context.getName());
}
m_context = null;
}
if (log().isDebugEnabled()) {
log().debug("Attempting to stop and join all stream handlers");
log().debug("There are " + m_receivers.size() + " receivers");
}
// stop all the receivers
int ndx = 0; // for tracing!
Iterator<TcpStreamHandler> i = m_receivers.iterator();
while (i.hasNext()) {
TcpStreamHandler t = i.next();
if (t.isAlive()) {
if (log().isDebugEnabled()) {
log().debug("Calling stop on handler index " + ndx);
}
t.stop();
if (log().isDebugEnabled()) {
log().debug("Stopped handler index " + ndx);
}
}
ndx++;
i.remove();
}
log().debug("All TCP Handlers are stopped and removed");
}
private ThreadCategory log() {
return ThreadCategory.getInstance(getClass());
}
/**
* Returns true if this runnable is executing.
*
* @return a boolean.
*/
public boolean isAlive() {
boolean rc = false;
if (m_context != null) {
rc = m_context.isAlive();
}
return rc;
}
/**
* The logic execution context to accept and process incoming connection
* requests. When a new connection is received a new thread of control is
* created to process the connection. This method encapsulates that control
* logic so that it can be executed in it's own java thread.
*/
public void run() {
// get the thread context for the ability to stop the process
m_context = Thread.currentThread();
synchronized (m_context) {
m_context.notifyAll();
}
// get the log information
ThreadCategory.setPrefix(m_logPrefix);
// check to see if the thread has already been stopped.
if (m_stop) {
log().debug("Stop flag set on thread startup");
try {
if (m_tcpSock != null) {
m_tcpSock.close();
}
log().debug("The socket has been closed");
} catch (Throwable e) {
log().warn("An exception occured closing the socket: " + e, e);
}
log().debug("Thread exiting");
return;
}
if (log().isDebugEnabled()) {
log().debug("Server connection processor started on " + m_ipAddress + ":" + m_tcpPort);
}
/*
*
* Set the initial timeout on the socket. This allows
* the thread to wake up every 1/2 second and check the
* shutdown status.
*/
try {
m_tcpSock.setSoTimeout(500);
} catch (SocketException e) {
if (!m_stop) {
log().warn("An I/O exception occured setting the socket timeout: " + e, e);
}
if (log().isDebugEnabled()) {
log().debug("Thread exiting due to socket error: " + e, e);
}
return;
}
// used to avoid seeing the trace message repeatedly
boolean ioInterrupted = false;
/*
* Check the status of the fiber and respond
* correctly. When the fiber enters a STOPPED or
* STOP PENDING state then shutdown occurs by exiting
* the while loop
*/
while (m_parent.getStatus() != Fiber.STOPPED && m_parent.getStatus() != Fiber.STOP_PENDING && !m_stop) {
try {
if (log().isDebugEnabled() && !ioInterrupted) {
log().debug("Waiting for new connection");
}
/*
* Get the newbie socket connection from the client.
* After accepting the connection start up a thread
* to process the request
*/
Socket newbie = m_tcpSock.accept();
ioInterrupted = false; // reset the flag
// build a connection string for the thread identifier
StringBuffer connection = new StringBuffer(InetAddressUtils.str(newbie.getInetAddress()));
connection.append(":").append(newbie.getPort());
if (log().isDebugEnabled()) {
log().debug("New connection accepted from " + connection);
}
// start a new handler
TcpStreamHandler handler = new TcpStreamHandler(m_parent, newbie, m_handlers, m_recsPerConn);
Thread processor = new Thread(handler, m_parent.getName() + "[" + connection + "]");
synchronized (processor) {
processor.start();
try {
processor.wait();
} catch (InterruptedException e) {
log().warn("The thread was interrupted: " + e, e);
}
}
log().debug("A new stream handler thread has been started");
// add the handler to the list
m_receivers.add(handler);
} catch (InterruptedIOException e) {
/*
* do nothing on interrupted I/O
* DON'T Continue, the end of the loop
* checks and removes terminated threads
*/
ioInterrupted = true;
} catch (IOException e) {
log().error("Server Socket I/O Error: " + e, e);
break;
}
/*
* Go through the threads in the list of
* receivers and find the dead ones. When
* they are no longer alive just remove them
* from the list.
*/
Iterator<TcpStreamHandler> i = m_receivers.iterator();
while (i.hasNext()) {
TcpStreamHandler t = i.next();
if (!t.isAlive()) {
i.remove();
}
}
}
// Either a fatal I/O error has occured or the service has been stopped.
try {
log().debug("closing the server socket connection");
m_tcpSock.close();
} catch (Throwable t) {
log().error("An I/O Error Occcured Closing the Server Socket: " + t, t);
}
// Log the termination of this runnable
log().debug("TCP Server Shutdown");
}
/**
* <p>setLogPrefix</p>
*
* @param prefix a {@link java.lang.String} object.
*/
public void setLogPrefix(String prefix) {
m_logPrefix = prefix;
}
/**
* <p>setEventsPerConnection</p>
*
* @param number a int.
*/
public void setEventsPerConnection(int number) {
m_recsPerConn = number;
}
}