org.eclipse.jubula.communication.Communicator.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jubula.communication.Communicator.java

Source

/*******************************************************************************
 * Copyright (c) 2004, 2010 BREDEX GmbH.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     BREDEX GmbH - initial API and implementation and/or initial documentation
 *******************************************************************************/
package org.eclipse.jubula.communication;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.Validate;
import org.eclipse.jubula.communication.connection.Connection;
import org.eclipse.jubula.communication.connection.ConnectionState;
import org.eclipse.jubula.communication.connection.DefaultServerSocket;
import org.eclipse.jubula.communication.connection.DefaultSocket;
import org.eclipse.jubula.communication.listener.ICommunicationErrorListener;
import org.eclipse.jubula.communication.listener.IErrorHandler;
import org.eclipse.jubula.communication.listener.IMessageHandler;
import org.eclipse.jubula.communication.message.Message;
import org.eclipse.jubula.communication.message.MessageHeader;
import org.eclipse.jubula.communication.message.MessageIdentifier;
import org.eclipse.jubula.communication.parser.MessageSerializer;
import org.eclipse.jubula.tools.exception.Assert;
import org.eclipse.jubula.tools.exception.AssertException;
import org.eclipse.jubula.tools.exception.CommunicationException;
import org.eclipse.jubula.tools.exception.JBVersionException;
import org.eclipse.jubula.tools.exception.SerialisationException;
import org.eclipse.jubula.tools.messagehandling.MessageIDs;
import org.slf4j.LoggerFactory;

/**
 * This class is the interface to the module communication. Responsibility:
 * <p>
 * <ul>
 * <li>create connections</li>
 * <li>sending/receiving messages of type 'Message'</li>
 * <li>identifying message as response of send messages with request()</li>
 * <li>creating corresponding command objects for received messages, calling
 * execute() for these command objects and sending back the response message.
 * <li>
 * </ul>
 * <p>
 * Instances of this class can act like a server or like a client. Use the
 * appropriate constructor. As a server, the instance will accept connections
 * without any timeout. The request to connect are not queued, so if a
 * connection indication arrives when the instance is already connected, the
 * connection is refused by the operating system.
 * <p>
 * How to use:
 * <ul>
 * <li>instantiate a communicator</li>
 * <li>add listeners</li>
 * <li>call run</li>
 * <li>send messages with send() or request()</li>
 * <li>to close the connection call close()</li>
 * See the package documentation for a detailed description.
 * 
 * @author BREDEX GmbH
 * @created 16.07.2004
 */
public class Communicator {
    /** timeout used as default value for establishing a connection in seconds */
    private static final int DEFAULT_CONNECTING_TIMEOUT = 10;

    /** timeout used as default value for request in seconds */
    private static final int DEFAULT_REQUEST_TIMEOUT = 10;

    /** constant to convert from seconds to milliseconds */
    private static final int THOUSAND = 1000;

    /** the logger instance */
    private static ConfigurableLogger log = new ConfigurableLogger(LoggerFactory.getLogger(Communicator.class));

    /** the port for connecting a server (this instance will act as a client) */
    private int m_port = 0;

    /**
     * the server to connect (this instance will act as a client), defaults to
     * null to distinguish whether this instance is a server or a client
     */
    private InetAddress m_inetAddress = null;

    /** class loader to get later the command Object for a message */
    private ClassLoader m_classLoader = null;

    /** the server socket (this instance will act as a server) */
    private DefaultServerSocket m_serverSocket = null;

    /** the local port, this instance uses, regardless which constructor was called */
    private int m_localPort;

    /** the connection to use */
    private Connection m_connection;

    /** listener listening to the connection for new message */
    private ConnectionListener m_connectionListener;

    /** listener listening to the connection for errors */
    private ErrorListener m_errorListener;

    /** a map for storing awaiting commands */
    private Map m_awaitingCommands;

    /** Set with ICommunicationErrorListeners listening to this communicator */
    private LinkedHashSet m_errorListeners;

    /** the connection manager, implementing the strategy for accepting connections */
    private ConnectionManager m_connectionManager;

    /** the exception handler for reading from the network, set to the used Connection */
    private IExceptionHandler m_exceptionHandler = new AbortingExceptionHandler();

    /** the parser converting from String to Message and vice versa */
    private MessageSerializer m_serializer;

    /** default timeout for establishing connections */
    private int m_initConnectionTimeout = DEFAULT_CONNECTING_TIMEOUT;

    /** flag for accepting thread to continue / stop accepting connections */
    private boolean m_accepting = false;

    /** timeout for requests */
    private int m_requestTimeout = DEFAULT_REQUEST_TIMEOUT;

    /** 
     * Mapping from client type to object responsible for initializing the 
     * connection. Connections for client types not contained within this 
     * mapping will be initialized in the default manner.
     *  
     * <code>String</code> => <code>IConnectionInitializer</code> 
     */
    private Map m_responseToInitializer;

    /**
     * The commandFactory for this communicator
     */
    private CommandFactory m_commandFactory;

    /**
     * Constructor explicitly setting the commandFactory.
     * @param inetAddress IP of target
     * @param port Port of target
     * @param cl class loader
     * @param cf CommandFactory for the communicator
     */
    public Communicator(InetAddress inetAddress, int port, ClassLoader cl, CommandFactory cf) {
        this(inetAddress, port, cl);
        m_commandFactory = cf;
    }

    /**
     * Use this constructor if the instance should act as client. The connection
     * will be established in run(). throws AssertException if parameters are null
     * @param inetAddress the inetAdress to connect
     * @param port the port the server listens
     * @param cl class loader to get the command object
     * @throws AssertException AssertException if parameters are null
     */
    public Communicator(InetAddress inetAddress, int port, ClassLoader cl) throws AssertException {
        super();
        // check parameter
        Assert.verify(inetAddress != null, "inetAddress must not be null"); //$NON-NLS-1$
        Assert.verify(port >= 0, "port must not be negativ"); //$NON-NLS-1$
        Assert.verify(cl != null, "no class loader for creation of command " + //$NON-NLS-1$
                "object available"); //$NON-NLS-1$

        // store inetAddress, port
        m_inetAddress = inetAddress;
        m_port = port;
        m_classLoader = cl;

        init();
    }

    /**
     * Use this constructor if the instance should act as a server. run() will start a thread accepting connections.
     * @param port the port to use, must not be negative, if port is zero, any
     *             free port will be used, query the opened port with getLocalPort()
     * @param cl class loader to get the command object
     * @throws AssertException if port is negative or factory is null.
     * @throws IOException if the given port can not used
     * @throws SecurityException if the security manager does not allow connections.
     */
    public Communicator(int port, ClassLoader cl) throws IOException, SecurityException, AssertException {

        this(port, cl, null);
    }

    /**
     * Use this constructor if the instance should act as a server. run() will start a thread accepting connections.
     * 
     * @param port the port to use, must not be negative, if port is zero, any
     *             free port will be used, query the opened port with getLocalPort()
     * @param cl class loader to get the command object
     * @param responseToInitializer Mapping from client type to connection 
     *                              initializer. Connections initiated by client
     *                              types not contained within this mapping will
     *                              be initialized in the default manner. May
     *                              be <code>null</code>.
     * @throws AssertException if port is negative or factory is null.
     * @throws IOException if the given port can not used
     * @throws SecurityException if the security manager does not allow connections.
     */
    public Communicator(int port, ClassLoader cl, Map responseToInitializer)
            throws IOException, SecurityException, AssertException {

        super();
        // check parameter
        Assert.verify(port >= 0, "port must not be negativ"); //$NON-NLS-1$
        Assert.verify(cl != null, "no class loader for creation of command " + //$NON-NLS-1$
                "object available"); //$NON-NLS-1$
        // create a server socket
        m_serverSocket = new DefaultServerSocket(port);
        m_serverSocket.setSoTimeout(0);

        // store the opened socket to LOCAL Port
        m_localPort = m_serverSocket.getLocalPort();
        m_classLoader = cl;

        m_responseToInitializer = new HashMap();
        if (responseToInitializer != null) {
            m_responseToInitializer.putAll(responseToInitializer);
        }

        Validate.allElementsOfType(m_responseToInitializer.keySet(), String.class);
        Validate.allElementsOfType(m_responseToInitializer.values(), IConnectionInitializer.class);

        init();
    }

    /**
     * private method for initialization, called from the constructors.
     */
    private void init() {
        m_serializer = new MessageSerializer();
        m_connection = null;
        setConnectionManager(new DefaultConnectionManager());
        // initialize map for awaiting commands
        m_awaitingCommands = new HashMap();
        // using a LinkedHashSet, because LinkedHashSet supports
        // ordered iteration and also supports remove, see
        // removeErrorHandler AND fire*-methods
        m_errorListeners = new LinkedHashSet();
        // create a connection listener for incoming commands
        m_connectionListener = new ConnectionListener();
        // create an error handler
        m_errorListener = new ErrorListener();
        // set default commandFactory
        m_commandFactory = new CommandFactory(m_classLoader);
    }

    /**
     * establish the connection, either connecting to a server or accepting
     * connections. This method will not block. If a connection could not made,
     * the listeners are notified with connectingFailed() and acceptingFailed
     * respectively.
     * 
     * @return the Thread responsible for accepting connections, or 
     *         <code>null</code> if the the receiver is acting as a client.
     * @throws SecurityException
     *             if the security manager does not allow connections.
     * @throws JBVersionException
     *             in case of version error between Client and AutStarter
     */
    public synchronized Thread run() throws SecurityException, JBVersionException {

        Thread acceptingThread = null;
        if (m_serverSocket != null && !isAccepting()) {
            // it's a server that hasn't yet started accepting connections
            setAccepting(true);
            acceptingThread = new AcceptingThread();
            acceptingThread.start();
        } else if (m_inetAddress != null) {
            // it's a client
            try {
                DefaultSocket socket = new DefaultSocket(m_inetAddress, m_port, m_initConnectionTimeout * THOUSAND);
                if (socket.isConnectionEstablished()) {
                    setup(socket);
                } else {
                    log.info("connecting failed with server state: " //$NON-NLS-1$
                            + String.valueOf(socket.getState()));
                    fireConnectingFailed(m_inetAddress, m_port);
                }
            } catch (IllegalArgumentException iae) {
                log.debug(iae.getLocalizedMessage(), iae);
                fireConnectingFailed(m_inetAddress, m_port);
            } catch (IOException ioe) {
                log.debug(ioe.getLocalizedMessage(), ioe);
                fireConnectingFailed(m_inetAddress, m_port);
            } catch (SecurityException se) {
                log.debug(se.getLocalizedMessage(), se);
                fireConnectingFailed(m_inetAddress, m_port);
                throw se;
            }
        }

        return acceptingThread;
    }

    /**
     * creates the appropriate command object for this message per reflection.
     * The message is set to the command.
     * @param msg message object
     * @throws UnknownCommandException -
     *             the exception thrown if the instantiation of command failed.
     * @return the created command
     */
    private ICommand createCommand(Message msg) throws UnknownCommandException {
        String commandClassName = msg.getCommandClass();
        ICommand result = m_commandFactory.createCommandObject(commandClassName);
        result.setMessage(msg);
        return result;
    }

    /**
     * registers the given listener
     * @param listener the listener, null objects are ignored
     */
    public void addCommunicationErrorListener(ICommunicationErrorListener listener) {
        if (listener != null) {
            synchronized (m_errorListeners) {
                m_errorListeners.add(listener);
            }
        }
    }

    /**
     * de-registers the given listener
     * @param listener the listener, null objects are ignored
     */
    public void removeCommunicationErrorListener(ICommunicationErrorListener listener) {
        if (listener != null) {
            synchronized (m_errorListeners) {
                m_errorListeners.remove(listener);
            }
        }
    }

    /**
     * @return Returns the defaultTimeOut in seconds.
     */
    public int getRequestTimeout() {
        return m_requestTimeout;
    }

    /**
     * @param defaultTimeOut The defaultTimeOut to set (in seconds).
     */
    public void setRequestTimeout(int defaultTimeOut) {
        m_requestTimeout = defaultTimeOut;
    }

    /**
     * @return Returns the defaultAcceptingTimeout in seconds.
     */
    public int getInitConnectionTimeout() {
        return m_initConnectionTimeout;
    }

    /**
     * @param initConnectionTimeout The timeout for initializing the connection to set (in seconds).
     */
    public void setInitConnectionTimeout(int initConnectionTimeout) {
        m_initConnectionTimeout = initConnectionTimeout;
    }

    /**
     * @return Returns the connectionManager.
     */
    public synchronized ConnectionManager getConnectionManager() {
        return m_connectionManager;
    }

    /**
     * @param connectionManager The connectionManager to set.
     */
    public synchronized void setConnectionManager(ConnectionManager connectionManager) {
        m_connectionManager = connectionManager;
    }

    /**
     * @return Returns the exceptionHandler.
     */
    public synchronized IExceptionHandler getExceptionHandler() {
        return m_exceptionHandler;
    }

    /**
     * @param exceptionHandler The exceptionHandler to set.
     */
    public synchronized void setExceptionHandler(IExceptionHandler exceptionHandler) {
        m_exceptionHandler = exceptionHandler;
    }

    /**
     * @return Returns the accepting.
     */
    private synchronized boolean isAccepting() {
        return m_accepting;
    }

    /**
     * @param accepting The accepting to set.
     */
    private synchronized void setAccepting(boolean accepting) {
        m_accepting = accepting;
    }

    /**
     * @return Returns the port this instance use.
     */
    public int getLocalPort() {
        return m_localPort;
    }

    /**
     * private method to check the state of this communicator 
     * @param methodName  the caller, for debugging purpose
     * @throws CommunicationException  if this communicator is not connected
     */
    private void checkConnectionState(String methodName) throws CommunicationException {
        if (m_connection == null) {
            log.debug("method " + methodName + //$NON-NLS-1$ 
                    " called to an unconnected " + //$NON-NLS-1$
                    "communicator"); //$NON-NLS-1$
            throw new CommunicationException("Communicator not connected", //$NON-NLS-1$
                    MessageIDs.E_COMMUNICATOR_CONNECTION);
        }
    }

    /**
     * send a message through this communicator. ICommand.execute will be called on the other site.
     * @param message  the message object, must not be null, otherwise an CommunicationException is thrown.
     * @throws CommunicationException
     *             if any error or exception occurs. A CommnunicationException
     *             is also thrown if this communicator is not connected, e.g.
     *             run() was not called, or exceptions at creation time were
     *             ignored
     */
    public void send(Message message) throws CommunicationException {
        checkConnectionState("send()"); //$NON-NLS-1$
        // check parameter
        if (message == null) {
            log.debug("method send() with null parameter called"); //$NON-NLS-1$
            throw new CommunicationException("no message to send", //$NON-NLS-1$
                    MessageIDs.E_NO_MESSAGE_TO_SEND);
        }
        try {
            message.setMessageId(new MessageIdentifier(m_connection.getNextSequenceNumber()));
            // create string message
            String messageToSend = m_serializer.serialize(message);
            // send message
            m_connection.send(new MessageHeader(MessageHeader.MESSAGE, message), messageToSend);
        } catch (SerialisationException se) {
            log.error(se.getLocalizedMessage(), se);
            throw new CommunicationException("could not send message:" //$NON-NLS-1$
                    + se.getMessage(), MessageIDs.E_MESSAGE_NOT_SEND);
        } catch (IOException ioe) {
            log.error(ioe.getLocalizedMessage(), ioe);
            throw new CommunicationException("io error occured during sending a message:" //$NON-NLS-1$
                    + ioe.getMessage(), MessageIDs.E_MESSAGE_SEND);
        } catch (IllegalArgumentException iae) {
            log.error(iae.getLocalizedMessage(), iae);
            throw new CommunicationException("message could not send", MessageIDs.E_MESSAGE_NOT_SEND); //$NON-NLS-1$
        }
    }

    /**
     * Send a message through this communicator and expect a response of type
     * command. ICommand.request will be called at the other site. If a response
     * was received the corresponding data will be set to the command object via
     * setData(). If the response was received during the given timeout,
     * command.response will be called. Otherwise command.timeout will be
     * called. This method will not block.
     * 
     * @param message -
     *            the Message to send, must not be null otherwise an
     *            CommunicationException is thrown.
     * @param command -
     *            the expected command, must not be null or a
     *            CommunicationException is thrown. If the command arrives in
     *            good time, the method execute() of the given instance will be
     *            called. If the commands arrives to late timeout() will be
     *            called.
     * @param timeout -
     *            max milliseconds to wait for a response. Only values greater 
     *            than zero are valid. For values less or equals to zero the
     *            configured default timeout will be used. If the timeout
     *            expires, the method timeout() in command will be called.
     * @throws CommunicationException
     *             if any error/exception occurs. A CommnunicationException is
     *             also thrown if this communicator is not connected, e.g. run()
     *             was not called, or exceptions at creation time were ignored
     */
    public void request(Message message, ICommand command, int timeout) throws CommunicationException {
        checkConnectionState("request()"); //$NON-NLS-1$
        // check parameter
        if (message == null) {
            log.debug("method request with null for parameter" //$NON-NLS-1$
                    + "message called."); //$NON-NLS-1$
            throw new CommunicationException("no message to send as request", MessageIDs.E_MESSAGE_NOT_TO_REQUEST); //$NON-NLS-1$
        }
        if (command == null) {
            log.debug("method request with null for parameter " //$NON-NLS-1$
                    + "command called"); //$NON-NLS-1$
            throw new CommunicationException("no command for receiving response", //$NON-NLS-1$
                    MessageIDs.E_NO_RECEIVING_COMMAND);
        }
        int timeoutToUse = m_requestTimeout;
        if (timeout <= 0) {
            log.debug("invalid timeout given to request: " + //$NON-NLS-1$
                    "using default timeout"); //$NON-NLS-1$
        } else {
            timeoutToUse = timeout;
        }
        MessageIdentifier messageIdentifier = new MessageIdentifier(m_connection.getNextSequenceNumber());
        try {
            message.setMessageId(messageIdentifier);
            // create string message
            String messageToSend = m_serializer.serialize(message);
            // put command into awaiting responses
            AwaitingCommand awaitingCommand = new AwaitingCommand(command, timeoutToUse);
            synchronized (m_awaitingCommands) {
                m_awaitingCommands.put(messageIdentifier, awaitingCommand);
            }
            // send message and start thread
            m_connection.send(new MessageHeader(MessageHeader.REQUEST, message), messageToSend);
            awaitingCommand.start();
        } catch (SerialisationException se) {
            log.error(se.getLocalizedMessage(), se);
            throw new CommunicationException("could not send message as request:" //$NON-NLS-1$
                    + se.getMessage(), MessageIDs.E_MESSAGE_NOT_TO_REQUEST);
        } catch (IOException ioe) {
            log.error(ioe.getLocalizedMessage(), ioe);
            synchronized (m_awaitingCommands) {
                m_awaitingCommands.remove(messageIdentifier);
            }
            throw new CommunicationException("io error occured during requesting a message: "//$NON-NLS-1$
                    + ioe.getMessage(), MessageIDs.E_MESSAGE_REQUEST);
        } catch (IllegalArgumentException iae) {
            log.error(iae.getLocalizedMessage(), iae);
            synchronized (m_awaitingCommands) {
                m_awaitingCommands.remove(messageIdentifier);
            }
            log.debug(iae.getLocalizedMessage(), iae);
            throw new CommunicationException("message could not send as a request", //$NON-NLS-1$
                    MessageIDs.E_MESSAGE_NOT_TO_REQUEST);
        }
    }

    /**
     * Closes the connection, calls to an unconnected connection will be ignored.
     */
    public synchronized void close() {
        if (m_connection != null) {
            Connection toClose = m_connection;
            m_connection = null;
            toClose.removeErrorHandler(m_errorListener);
            toClose.close();
            getConnectionManager().remove(toClose);
            m_errorListener.shutDown();
        } else {
            if (log.isDebugEnabled()) {
                log.debug("close() called for an unconnected communicator"); //$NON-NLS-1$
            }
        }
    }

    /**
     * notifies the error listener with sendFailed(); 
     * @param header  the header
     * @param message  the message
     */
    private synchronized void fireSendFailed(MessageHeader header, String message) {
        if (log.isInfoEnabled()) {
            log.info("firing sendFailed, message=" + message); //$NON-NLS-1$
        }
        try {
            Message data = m_serializer.deserialize(header, message);
            Iterator iter = ((HashSet) ((HashSet) m_errorListeners).clone()).iterator();
            while (iter.hasNext()) {
                try {
                    ((ICommunicationErrorListener) iter.next()).sendFailed(data);
                } catch (Throwable t) {
                    log.error("Exception while calling listener", //$NON-NLS-1$
                            t);
                }
            }
        } catch (SerialisationException se) {
            // serialization failed -> log
            log.error("deserialisation of\n" + message + //$NON-NLS-1$
                    "\nduring notifying " + //$NON-NLS-1$
                    "CommunicationErrorListeners " + //$NON-NLS-1$
                    "with sendFailed() failed", se); //$NON-NLS-1$
        }
    }

    /**
     * notifies the error listener with shutDown <br>
     * notifies the connection manager also
     */
    private synchronized void fireShutDown() {
        log.info("firing shutDown"); //$NON-NLS-1$ 
        // notify the error listener
        Iterator iter = ((HashSet) ((HashSet) m_errorListeners).clone()).iterator();
        while (iter.hasNext()) {
            try {
                ((ICommunicationErrorListener) iter.next()).shutDown();
            } catch (Throwable t) {
                log.error("Exception while calling listener", //$NON-NLS-1$
                        t);
            }
        }
        // notify the connection manager
        getConnectionManager().remove(m_connection);
    }

    /**
     * notifies the error listener with acceptingFailed 
     * @param port  the used port for accepting
     */
    private synchronized void fireAcceptingFailed(int port) {
        if (log.isInfoEnabled()) {
            log.info("firing acceptingFailed"); //$NON-NLS-1$
        }
        Iterator iter = ((HashSet) ((HashSet) m_errorListeners).clone()).iterator();

        while (iter.hasNext()) {
            try {
                ((ICommunicationErrorListener) iter.next()).acceptingFailed(port);
            } catch (Throwable t) {
                log.error("Exception while calling listener", //$NON-NLS-1$
                        t);
            }
        }
    }

    /**
     * notifies the listener with connectingFailed() 
     * @param inetAddress the remote address
     * @param port the remote port
     */
    private synchronized void fireConnectingFailed(InetAddress inetAddress, int port) {

        if (log.isDebugEnabled()) {
            log.debug("firing connectingFailed"); //$NON-NLS-1$
        }
        Iterator iter = ((HashSet) ((HashSet) m_errorListeners).clone()).iterator();
        while (iter.hasNext()) {
            try {
                ((ICommunicationErrorListener) iter.next()).connectingFailed(inetAddress, port);
            } catch (Throwable t) {
                log.error("Exception while calling listener", t); //$NON-NLS-1$
            }
        }
    }

    /**
     * notifies the listeners with connectionGained <br>
     * @param inetAddress the remote address
     * @param port the remote port
     */
    private synchronized void fireConnectionGained(InetAddress inetAddress, int port) {

        log.info("firing connectionGained"); //$NON-NLS-1$

        // notify the listeners
        Iterator iter = ((HashSet) ((HashSet) m_errorListeners).clone()).iterator();
        while (iter.hasNext()) {
            try {
                ((ICommunicationErrorListener) iter.next()).connectionGained(inetAddress, port);
            } catch (Throwable t) {
                log.error("Exception while calling listener", //$NON-NLS-1$
                        t);
            }
        }
    }

    /**
     * setting the up the connection with the given socket
     * @param socket the socket, gained by connecting (Socket.Constructor)
     */
    private void setup(DefaultSocket socket) throws IOException {
        m_connection = new Connection(socket);
        setup(m_connection, socket);
    }

    /**
     * setting the up the connection with the given socket
     * @param socket the socket, gained by accepting (on a ServerSocket)
     * @param bufferedReader The input stream reader for the given socket.
     */
    private void setup(Socket socket, BufferedReader bufferedReader) {
        m_connection = new Connection(socket, bufferedReader);
        setup(m_connection, socket);
    }

    /**
     * Initializes the given connection and socket, adding necessary listeners
     * and starting to read the input stream of the socket.
     * 
     * @param conn The connection to initialize.
     * @param socket The socket associated with the connection.
     */
    private void setup(Connection conn, Socket socket) {
        // add listener
        conn.addMessageHandler(m_connectionListener);
        conn.addErrorHandler(m_errorListener);
        // set an exceptionHandler
        conn.setExceptionHandler(getExceptionHandler());
        // start reading from connection
        String id = socket.toString();
        conn.startReading(id);
        fireConnectionGained(socket.getInetAddress(), socket.getPort());
    }

    /**
     * Listener implementing IMessageHandler. creates the command objects and
     * calls the execute method in the command objects. uses Hashmap with awaiting commands synchronized
     * @author BREDEX GmbH
     * @created 16.07.2004
     */
    private class ConnectionListener implements IMessageHandler {

        /**
         * {@inheritDoc}
         */
        public void received(MessageHeader header, String message) {
            if (log.isDebugEnabled()) {
                log.debug("received message:" + message); //$NON-NLS-1$
            }
            try {
                // deserialize message
                Message data = m_serializer.deserialize(header, message);
                MessageIdentifier receivedMessageId = data.getMessageId();
                MessageIdentifier boundedId = data.getBindId();
                // boundedId is the key in the awaiting commands map
                ICommand command = null;
                AwaitingCommand awaitingCommand = null;
                if (boundedId != null) {
                    // data is an answer
                    // it's an awaiting command too ?
                    synchronized (m_awaitingCommands) {
                        awaitingCommand = (AwaitingCommand) m_awaitingCommands.get(boundedId);
                    }
                }
                if (awaitingCommand != null) {
                    // yes, it's an awaiting command
                    // remove it from the map
                    synchronized (m_awaitingCommands) {
                        m_awaitingCommands.remove(boundedId);
                    }
                    if (!data.getCommandClass().equals(awaitingCommand.getCommand().getClass().getName())) {
                        log.error("answer is of wrong type"); //$NON-NLS-1$
                        // RETURN FROM HERE
                        return;
                    }
                    if (awaitingCommand.isTimeoutExpired()) {
                        // timeout expired, method timeout() already called
                        // just finish
                        // RETURN FROM HERE
                        log.warn("Received response " + awaitingCommand.getCommand() + " *after* timeout expired."); //$NON-NLS-1$ //$NON-NLS-2$
                        return;
                    }
                    // timeout not expired, stop the thread
                    log.debug("Received command response for " + awaitingCommand.getCommand()); //$NON-NLS-1$
                    awaitingCommand.commandReceived();
                    command = awaitingCommand.getCommand();
                }
                if (command == null) {
                    // create a new command, the message is set in createCommand
                    command = createCommand(data);
                } else {
                    command.setMessage(data); // fill message
                }
                // call execute(), catch any exception
                Message response = null;
                try {
                    response = command.execute();
                } catch (Throwable t) {
                    log.error("catched exception from '" //$NON-NLS-1$
                            + command.getClass().getName() + ".execute()'", t); //$NON-NLS-1$
                }
                if (response != null) {
                    log.debug("Sending response: " + response); //$NON-NLS-1$
                    // mark response as answer of the received message
                    response.setBindId(receivedMessageId);
                    send(response); // send message back
                }
            } catch (ClassCastException cce) {
                log.error("wrong type in the map of awaiting responses", cce); //$NON-NLS-1$
            } catch (SerialisationException se) {
                log.error("deserialisation of a received message failed", se); //$NON-NLS-1$
            } catch (UnknownCommandException uce) {
                log.error("received message with unknown command", uce); //$NON-NLS-1$
            } catch (CommunicationException ce) {
                log.error("could not send answer ", ce); //$NON-NLS-1$
            }
        }
    }

    /**
     * class for listening to the connection for errors, notifies the listeners, registered to the communicator
     * @author BREDEX GmbH
     * @created 23.07.2004
     */
    private class ErrorListener implements IErrorHandler {

        /**
         * {@inheritDoc}
         */
        public void sendFailed(MessageHeader header, String message) {
            fireSendFailed(header, message);
        }

        /**
         * {@inheritDoc}
         */
        public void shutDown() {
            fireShutDown();
        }
    }

    /**
     * class to put instances of into the hash map of awaiting commands. It's also the thread to handle timeouts for requests.
     * @author BREDEX GmbH
     * @created 20.07.2004
     */
    private static class AwaitingCommand extends Thread {
        /** flag if timeout has expires */
        private boolean m_timeoutExpired;

        /** reference to the command */
        private ICommand m_command;

        /** the timeout in milliseconds */
        private long m_timeout;

        /** indicates whether the awaited command has been received */
        private boolean m_wasCommandReceived;

        /**
         * default constructor 
         * @param command the awaiting command
         * @param timeout the time in milliseconds when command.timeout() will be called
         */
        public AwaitingCommand(ICommand command, int timeout) {
            super("Awaiting command: " + command.getClass()); //$NON-NLS-1$
            m_command = command;
            m_timeout = timeout;
            m_wasCommandReceived = false;
            setTimeoutExpired(false);
        }

        /**
         * @return Returns the command.
         */
        public ICommand getCommand() {
            return m_command;
        }

        /**
         * @return Returns the timeoutExpired.
         */
        public synchronized boolean isTimeoutExpired() {
            return m_timeoutExpired;
        }

        /**
         * @param timeoutExpired The timeoutExpired to set.
         */
        private synchronized void setTimeoutExpired(boolean timeoutExpired) {
            m_timeoutExpired = timeoutExpired;
        }

        /**
         * {@inheritDoc}
         */
        public void run() {
            // only go through the sleep process if the command has 
            // not yet arrived
            if (!wasCommandReceived()) {
                long startTime = System.currentTimeMillis();
                while (!wasCommandReceived() && startTime + m_timeout >= System.currentTimeMillis()) {

                    try {
                        sleep(200);
                    } catch (InterruptedException ie) {
                        // Do nothing.
                        // If a valid interrupt occurred, then the loop will end
                        // on next iteration because the command was received.
                    }
                }

                // one last check whether the command was received after
                // the waiting period
                if (!wasCommandReceived()) {
                    // timeout occurs: set flag
                    setTimeoutExpired(true);
                    // call timeout in the command, catch any exception
                    try {
                        m_command.timeout();
                    } catch (Exception e) {
                        log.error("catched exception from '" //$NON-NLS-1$
                                + m_command.getClass().getName() + ".timeout()'", e); //$NON-NLS-1$
                    }
                }
            }
        }

        /**
         * Notes that the command was received in good time.
         */
        public synchronized void commandReceived() {
            m_wasCommandReceived = true;
            interrupt();
        }

        /**
         * @return the wasCommandReceived
         */
        public synchronized boolean wasCommandReceived() {
            return m_wasCommandReceived;
        }
    }

    /**
     * a thread accepting connections, so Communicator.run() will not block 
     * @author BREDEX GmbH
     * @created 29.07.2004
     */
    private class AcceptingThread extends Thread {

        /**
         * Constructor
         */
        public AcceptingThread() {
            super("Accepting Thread - listening on port " //$NON-NLS-1$
                    + m_serverSocket.getLocalPort());
        }

        /**
         * {@inheritDoc}
         */
        public void run() {
            while (isAccepting() && !Thread.currentThread().isInterrupted()) {
                try {
                    Socket socket = m_serverSocket.accept();
                    BufferedReader reader = new BufferedReader(
                            new InputStreamReader(socket.getInputStream(), Connection.IO_STREAM_ENCODING));

                    // find out what kind of client initiated the connection
                    String response = DefaultServerSocket.requestClientType(socket, reader,
                            DEFAULT_CONNECTING_TIMEOUT * THOUSAND);

                    if (response != null) {
                        IConnectionInitializer initializer = (IConnectionInitializer) m_responseToInitializer
                                .get(response);
                        if (initializer != null) {
                            initializer.initConnection(socket, reader);
                        } else {
                            // use default initialization strategy
                            int nextState = getConnectionManager().getNextState();
                            DefaultServerSocket.send(socket, nextState);
                            if (nextState == ConnectionState.SERVER_OK) {
                                setup(socket, reader);
                                // notify the connection manager
                                getConnectionManager().add(m_connection);
                            }
                        }
                    } else {
                        // No useful response from client. 
                        // Just close the socket.
                        socket.close();
                    }

                } catch (IOException ioe) {
                    log.debug(ioe.getLocalizedMessage(), ioe);
                    fireAcceptingFailed(m_serverSocket.getLocalPort());
                    // HERE exception manager if the IOExceptions
                    // are numerous, at the moment stop accepting
                    setAccepting(false);
                } catch (Throwable t) {
                    log.error(t.getLocalizedMessage(), t);
                    setAccepting(false);
                    // HERE exception handler for accepting ?
                    // setAccepting(getAcceptingExceptionHandler().handle(t));
                }
            }
        }
    }

    /**
     * Interface for the connection manager. 
     * @author BREDEX GmbH
     * @created 21.09.2004
     */
    public interface ConnectionManager {
        /**
         * the state to send at next accept
         * @return a constant from ConnectionState
         */
        public int getNextState();

        /**
         * a new connection was created
         * @param connection the new connection
         */
        public void add(Connection connection);

        /**
         * the connection was closed
         * @param connection the closed connection
         */
        public void remove(Connection connection);

    }

    /**
     * Default manager, accepts exact one connection 
     * @author BREDEX GmbH
     * @created 21.09.2004
     */
    private static class DefaultConnectionManager implements ConnectionManager {
        /** number of maximum connections to accept, this implementation accepts just one */
        private static final int BACKLOG = 1;

        /** list with accepted connections */
        private List m_connections;

        /**
         * default constructor <br>
         */
        public DefaultConnectionManager() {
            m_connections = new ArrayList(BACKLOG);
        }

        /**
         * @return the state to send at next accept
         */
        public int getNextState() {
            if (m_connections.size() < BACKLOG) {
                return ConnectionState.SERVER_OK;
            }
            return ConnectionState.SERVER_BUSY;
        }

        /**
         * start managing the given connection, just put it into the list
         * @param connection the connection to manage
         */
        public void add(Connection connection) {
            m_connections.add(connection);
        }

        /**
         * stop managing the given connection, just remove it from the list
         * @param connection the connection to remove
         */
        public void remove(Connection connection) {
            m_connections.remove(connection);
        }
    }

    /**
     * prepare the communicator for connection problems
     */
    public void prepareForConnectionProblems() {
        setEnablementOfConnectionLogger(false);
    }

    /**
     * @param enable
     *            the loggers enablement
     * 
     */
    private void setEnablementOfConnectionLogger(boolean enable) {
        Connection c = getConnection();
        if (c != null) {
            c.getLogger().setEnabled(enable);
        }
    }

    /**
     * Interrupts all timeouts
     * Call this when the TestExecution gets interrupted.
     */
    public void interruptAllTimeouts() {
        Set keys = new HashMap(m_awaitingCommands).keySet();
        Iterator iter = keys.iterator();
        while (iter.hasNext()) {
            final Object key = iter.next();
            AwaitingCommand cmd = (AwaitingCommand) m_awaitingCommands.get(key);
            cmd.commandReceived();
            m_awaitingCommands.remove(key);
        }
    }

    /**
     * @return Returns the port.
     */
    public int getPort() {
        return m_port;
    }

    /**
     * @return Returns the inetAddress.
     */
    public String getHostName() {
        return m_inetAddress.getHostName();
    }

    /**
     * @return Returns the connection.
     */
    public Connection getConnection() {
        return m_connection;
    }

    /**
     * Clears the list of error listeners.
     */
    public void clearListeners() {
        m_errorListeners.clear();
        if (m_connection != null) {
            m_connection.clearListeners();
        }
    }
}