// Copyright (C) 2000 - 2012 Philip Aston // All rights reserved. // // This file is part of The Grinder software distribution. Refer to // the file LICENSE which is part of The Grinder distribution for // licensing details. The Grinder distribution is available on the // Internet at http://grinder.sourceforge.net/ // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED // OF THE POSSIBILITY OF SUCH DAMAGE. package net.grinder.console.communication; import net.grinder.communication.*; import net.grinder.console.common.DisplayMessageConsoleException; import net.grinder.console.common.ErrorHandler; import net.grinder.console.common.Resources; import net.grinder.console.model.ConsoleCommunicationSetting; import net.grinder.console.model.ConsoleProperties; import net.grinder.util.TimeAuthority; import net.grinder.util.thread.BooleanCondition; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.Thread.UncaughtExceptionHandler; import static org.ngrinder.common.util.NoOp.noOp; /** * Handles communication for the console. This is the extension of * {@link ConsoleCommunicationImplementation}. * * @author JunHo Yoon * @see ConsoleCommunicationImplementation * @since 3.0 */ public final class ConsoleCommunicationImplementationEx implements ConsoleCommunication { private final Resources m_resources; private final ConsoleProperties m_properties; private final ErrorHandler m_errorHandler; private final TimeAuthority m_timeAuthority; private final long m_idlePollDelay; private long m_inactiveClientTimeOut; private final MessageDispatchSender m_messageDispatcher = new MessageDispatchSender(); private final BooleanCondition m_processing = new BooleanCondition(); private final BooleanCondition m_shutdown = new BooleanCondition(); private Acceptor m_acceptor = null; private ServerReceiver m_receiver = null; private FanOutServerSender m_sender = null; private Thread m_acceptorProblemListener = null; private AcceptorResolver acceptorResolver = null; /** * Constructor. * * @param resources Resources. * @param properties Console properties. * @param errorHandler Error handler. * @param timeAuthority Knows the time * @throws DisplayMessageConsoleException If properties are invalid. */ @SuppressWarnings("UnusedDeclaration") public ConsoleCommunicationImplementationEx(Resources resources, ConsoleProperties properties, ErrorHandler errorHandler, TimeAuthority timeAuthority, ConsoleCommunicationSetting consoleCommunicationSetting) throws DisplayMessageConsoleException { m_resources = resources; m_properties = properties; m_errorHandler = errorHandler; m_timeAuthority = timeAuthority; if (consoleCommunicationSetting == null) { consoleCommunicationSetting = ConsoleCommunicationSetting.asDefault(); } m_idlePollDelay = consoleCommunicationSetting.getIdlePollDelay(); m_inactiveClientTimeOut = consoleCommunicationSetting.getInactiveClientTimeOut(); properties.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { final String property = event.getPropertyName(); if (property.equals(ConsoleProperties.CONSOLE_HOST_PROPERTY) || property.equals(ConsoleProperties.CONSOLE_PORT_PROPERTY)) { reset(); } } }); reset(); } private void reset() { if (m_acceptorProblemListener != null) { m_acceptorProblemListener.interrupt(); m_acceptorProblemListener = null; } try { if (m_acceptor != null) { m_acceptor.shutdown(); } } catch (CommunicationException e) { m_errorHandler.handleException(e); return; } if (m_sender != null) { m_sender.shutdown(); } if (m_receiver != null) { m_receiver.shutdown(); // Wait until we're deaf. This requires that some other thread // executes // processOneMessage(). We can't suck on m_receiver ourself as there // may // be valid pending messages queued up. m_processing.await(false); } if (m_shutdown.get()) { return; } try { m_acceptor = new Acceptor(m_properties.getConsoleHost(), m_properties.getConsolePort(), 1, m_timeAuthority); acceptorResolver = new AcceptorResolver(); acceptorResolver.addSocketListener(m_acceptor); } catch (CommunicationException e) { m_errorHandler.handleException(new DisplayMessageConsoleException(m_resources, "localBindError.text", e)); // Wake up any threads waiting in processOneMessage(). m_processing.wakeUpAllWaiters(); return; } m_acceptorProblemListener = new Thread("Acceptor problem listener") { public void run() { while (true) { Exception exception = null; try { exception = m_acceptor.getPendingException(); } catch (Exception e) { // FALL THROUGH noOp(); } if (exception == null) { // Acceptor is shutting down. break; } m_errorHandler.handleException(exception); } } }; m_acceptorProblemListener.setDaemon(true); // Ignore any exception in acceptor problem listener m_acceptorProblemListener.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { m_errorHandler.handleInformationMessage(e.getMessage()); } }); m_acceptorProblemListener.start(); m_receiver = new ServerReceiver(); try { m_receiver.receiveFrom(m_acceptor, new ConnectionType[]{ConnectionType.AGENT, ConnectionType.CONSOLE_CLIENT, ConnectionType.WORKER,}, 5, m_idlePollDelay, m_inactiveClientTimeOut); } catch (CommunicationException e) { throw new AssertionError(e); } try { m_sender = new FanOutServerSender(m_acceptor, ConnectionType.AGENT, 3); } catch (Acceptor.ShutdownException e) { // I am tempted to make this an assertion. // Currently, this condition can only happen if the accept() call // throws // an exception. I guess this might reasonably happen if a network // i/f // goes away immediately after we create the Acceptor. It's not easy // for // us to reset ourselves at this point (I certainly don't want to // recurse), so we notify the user. Users could get going again by // reseting new console address info, but most likely they'll just // restart // the console. m_processing.wakeUpAllWaiters(); m_errorHandler.handleException(e); return; } m_processing.set(true); } /** * Returns the message dispatch registry which callers can use to register new message handlers. * * @return The registry. */ public MessageDispatchRegistry getMessageDispatchRegistry() { return m_messageDispatcher; } /** * Shut down communication. */ public void shutdown() { m_shutdown.set(true); m_processing.set(false); reset(); } /** * Wait to receive a message, then process it. * * @return <code>true</code> if we processed a message successfully; <code>false</code> if we've * been shut down. * @see #shutdown() */ public boolean processOneMessage() { while (true) { if (m_shutdown.get()) { return false; } if (m_processing.await(true)) { try { final Message message = m_receiver.waitForMessage(); if (message == null) { // Current receiver has been shut down. m_processing.set(false); } else { m_messageDispatcher.send(message); return true; } } catch (CommunicationException e) { // The receive or send failed. We only set m_processing to // false when // our receiver has been shut down. m_errorHandler.handleException(e); } } } } public String getLocalConnectingAddress(Address agentAddress) { return acceptorResolver.getServerAddress(agentAddress); } /** * The number of connections that have been accepted and are still active. Used by the unit * tests. * * @return The number of accepted connections. */ @SuppressWarnings("UnusedDeclaration") public int getNumberOfConnections() { return m_acceptor == null ? 0 : m_acceptor.getNumberOfConnections(); } /** * Send the given message to the agent processes (which may pass it on to their workers). * * <p> * Any errors that occur will be handled with the error handler. * </p> * * @param message The message to send. */ public void sendToAgents(Message message) { if (m_sender == null) { m_errorHandler.handleErrorMessage(m_resources.getString("sendError.text")); } else { try { m_sender.send(message); } catch (CommunicationException e) { m_errorHandler.handleException(new DisplayMessageConsoleException(m_resources, "sendError.text", e)); } } } /** * Send the given message to the given agent processes (which may pass it on to its workers). * * <p> * Any errors that occur will be handled with the error handler. * </p> * * @param address The address to which the message should be sent. * @param message The message to send. */ public void sendToAddressedAgents(Address address, Message message) { if (m_sender == null) { m_errorHandler.handleErrorMessage(m_resources.getString("sendError.text")); } else { try { m_sender.send(address, message); } catch (CommunicationException e) { m_errorHandler.handleException(new DisplayMessageConsoleException(m_resources, "sendError.text", e)); } } } }