/******************************************************************************* * 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.internal; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; 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.internal.connection.Connection; import org.eclipse.jubula.communication.internal.connection.ConnectionState; import org.eclipse.jubula.communication.internal.connection.DefaultClientSocket; import org.eclipse.jubula.communication.internal.connection.DefaultServerSocket; import org.eclipse.jubula.communication.internal.listener.ICommunicationErrorListener; import org.eclipse.jubula.communication.internal.listener.IErrorHandler; import org.eclipse.jubula.communication.internal.listener.IMessageHandler; import org.eclipse.jubula.communication.internal.message.Message; import org.eclipse.jubula.communication.internal.message.MessageHeader; import org.eclipse.jubula.communication.internal.message.MessageIdentifier; import org.eclipse.jubula.communication.internal.parser.MessageSerializer; import org.eclipse.jubula.tools.internal.exception.Assert; import org.eclipse.jubula.tools.internal.exception.AssertException; import org.eclipse.jubula.tools.internal.exception.CommunicationException; import org.eclipse.jubula.tools.internal.exception.JBVersionException; import org.eclipse.jubula.tools.internal.exception.SerialisationException; import org.eclipse.jubula.tools.internal.messagehandling.MessageIDs; import org.eclipse.jubula.tools.internal.utils.IsAliveThread; 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 */ public static final int DEFAULT_CONNECTING_TIMEOUT = 20; /** timeout for so server socket */ private static final int INFINITE = 0; /** 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<MessageIdentifier, AwaitingCommand> m_awaitingCommands; /** Set with ICommunicationErrorListeners listening to this communicator */ private LinkedHashSet<ICommunicationErrorListener> 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; /** flag for accepting thread to continue / stop accepting connections */ private boolean m_accepting = false; /** * 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<String, IConnectionInitializer> m_responseToInitializer; /** * The commandFactory for this communicator */ private CommandFactory m_commandFactory; /** * boolean if the server socket could be closed, default is * <code>true</code>. Set this variable only to <code>false</code> if the * communicator will be reused for other connections since than the * serverSocket would not be closed */ private boolean m_isServerSocketClosable = true; /** * 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<String, IConnectionInitializer> 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(INFINITE); // store the opened socket to LOCAL Port m_localPort = m_serverSocket.getLocalPort(); m_classLoader = cl; m_responseToInitializer = new HashMap<String, IConnectionInitializer>(); 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<MessageIdentifier, AwaitingCommand>(); // using a LinkedHashSet, because LinkedHashSet supports // ordered iteration and also supports remove, see // removeErrorHandler AND fire*-methods m_errorListeners = new LinkedHashSet<ICommunicationErrorListener>(); // 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.setDaemon(true); acceptingThread.start(); } else if (m_inetAddress != null) { // it's a client try { DefaultClientSocket socket = new DefaultClientSocket( m_inetAddress, m_port, DEFAULT_CONNECTING_TIMEOUT * 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; } /** * * @param isServerSocketClosable * the default is <code>true</code>, if you want to have a * {@link Communicator} which server socket would not be closed * after it was closed, set it to <code>false</code> */ public void setIsServerSocketClosable(boolean isServerSocketClosable) { m_isServerSocketClosable = isServerSocketClosable; } /** * 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 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.debug(se.getLocalizedMessage(), se); throw new CommunicationException( "could not send message:" //$NON-NLS-1$ + se.getMessage(), se, MessageIDs.E_MESSAGE_NOT_SEND); } catch (IOException ioe) { log.debug(ioe.getLocalizedMessage(), ioe); throw new CommunicationException( "io error occured during sending a message:" //$NON-NLS-1$ + ioe.getMessage(), ioe, MessageIDs.E_MESSAGE_SEND); } catch (IllegalArgumentException iae) { log.debug(iae.getLocalizedMessage(), iae); throw new CommunicationException( "message could not send", iae, 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 { try { requestImpl(message, command, timeout, false); } catch (InterruptedException e) { // this can not happen due to calling requestImpl block = false log.error(e.getLocalizedMessage(), e); } } /** * 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. The calling thread of this method will wait until a response is * received or the timeout is reached. * * @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 requestAndWait(Message message, ICommand command, int timeout) throws CommunicationException, InterruptedException { requestImpl(message, command, timeout, true); } /** * 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. The calling thread of this method will wait until a response is * received or the timeout is reached. * * @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. * @param wait * whether this method should block (wait) for the command being executed * @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 */ private void requestImpl(Message message, ICommand command, int timeout, boolean wait) throws CommunicationException, InterruptedException { 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 = DEFAULT_REQUEST_TIMEOUT; 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(); if (wait) { awaitingCommand.join(timeoutToUse); } } 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$ } } if (m_serverSocket != null && m_isServerSocketClosable) { try { m_serverSocket.close(); } catch (IOException e) { log.info("Exception in closing server Socket", e); //$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(DefaultClientSocket 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 = 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("caught 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 IsAliveThread { /** 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 IsAliveThread { /** * 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(); final InputStream inputStream = socket.getInputStream(); BufferedReader reader = new BufferedReader( new InputStreamReader(inputStream, Connection.IO_STREAM_ENCODING)); // find out what kind of client initiated the connection String response = DefaultServerSocket.requestClientType( socket, reader, inputStream, DEFAULT_CONNECTING_TIMEOUT * THOUSAND); if (response != null) { IConnectionInitializer initializer = 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 property name for the change listener*/ public static String PROP_CONNECTION_CHANGE = "connection_changed"; //$NON-NLS-1$ /** * 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); /** * @param listener add the listener */ public void addPropertyChangedListener(PropertyChangeListener listener); /** * * @param listener removes the listener */ public void removePropertyChangedListener( PropertyChangeListener listener); } /** * 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; /** logger */ private static ConfigurableLogger cmLogger = new ConfigurableLogger( LoggerFactory.getLogger(DefaultConnectionManager.class)); /** list with accepted connections */ private List<Connection> m_connections; /** property change support for adding and removing connections */ private PropertyChangeSupport m_propertyChangeSupport = new PropertyChangeSupport(this); /** * default constructor <br> */ public DefaultConnectionManager() { m_connections = new ArrayList<Connection>(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); try { m_propertyChangeSupport.firePropertyChange( ConnectionManager.PROP_CONNECTION_CHANGE, m_connections.size() - 1, m_connections.size()); } catch (Exception e) { cmLogger.warn("exception during calling of listeners", e); //$NON-NLS-1$ } } /** * 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); try { m_propertyChangeSupport.firePropertyChange( ConnectionManager.PROP_CONNECTION_CHANGE, m_connections.size() + 1, m_connections.size()); } catch (Exception e) { cmLogger.warn("exception during calling of listeners", e); //$NON-NLS-1$ } } /** * {@inheritDoc} */ public void addPropertyChangedListener( PropertyChangeListener listener) { m_propertyChangeSupport.addPropertyChangeListener(listener); } /** * {@inheritDoc} */ public void removePropertyChangedListener( PropertyChangeListener listener) { m_propertyChangeSupport.removePropertyChangeListener(listener); } } /** * 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<MessageIdentifier> keys = new HashMap <MessageIdentifier, AwaitingCommand>( m_awaitingCommands).keySet(); Iterator iter = keys.iterator(); while (iter.hasNext()) { final Object key = iter.next(); AwaitingCommand cmd = 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(); } } }