
net.grinder.console.communication.ConsoleCommunicationImplementation Maven / Gradle / Ivy
The newest version!
// 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 java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.annotation.PreDestroy;
import net.grinder.communication.Acceptor;
import net.grinder.communication.Address;
import net.grinder.communication.CommunicationException;
import net.grinder.communication.ConnectionType;
import net.grinder.communication.FanOutServerSender;
import net.grinder.communication.Message;
import net.grinder.communication.MessageDispatchRegistry;
import net.grinder.communication.MessageDispatchSender;
import net.grinder.communication.ServerReceiver;
import net.grinder.console.common.DisplayMessageConsoleException;
import net.grinder.console.common.ErrorHandler;
import net.grinder.console.common.Resources;
import net.grinder.console.model.ConsoleProperties;
import net.grinder.util.TimeAuthority;
import net.grinder.util.thread.BooleanCondition;
/**
* Handles communication for the console.
*
* @author Philip Aston
*/
public final class ConsoleCommunicationImplementation
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 final 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;
/**
* Constructor that uses a default idlePollDelay.
*
* @param resources
* Resources.
* @param properties
* Console properties.
* @param errorHandler
* Error handler.
* @param timeAuthority
* Knows the time
* @throws DisplayMessageConsoleException
* If properties are invalid.
*/
public ConsoleCommunicationImplementation(Resources resources,
ConsoleProperties properties,
ErrorHandler errorHandler,
TimeAuthority timeAuthority)
throws DisplayMessageConsoleException {
this(resources, properties, errorHandler, timeAuthority, 500, 30000);
}
/**
* Constructor.
*
* @param resources
* Resources.
* @param properties
* Console properties.
* @param errorHandler
* Error handler.
* @param timeAuthority
* Knows the time
* @param idlePollDelay
* Time in milliseconds that our ServerReceiver threads should sleep
* for if there's no incoming messages.
* @param inactiveClientTimeOut
* How long before we consider a client connection that presents no
* data to be inactive.
* @throws DisplayMessageConsoleException
* If properties are invalid.
*/
public ConsoleCommunicationImplementation(Resources resources,
ConsoleProperties properties,
ErrorHandler errorHandler,
TimeAuthority timeAuthority,
long idlePollDelay,
long inactiveClientTimeOut)
throws DisplayMessageConsoleException {
m_resources = resources;
m_properties = properties;
m_errorHandler = errorHandler;
m_timeAuthority = timeAuthority;
m_idlePollDelay = idlePollDelay;
m_inactiveClientTimeOut = inactiveClientTimeOut;
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() {
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);
}
catch (CommunicationException e) {
m_errorHandler.handleException(
new DisplayMessageConsoleException(
m_resources, "localBindError.text", e));
// Wake up any threads waiting in processOneMessage().
m_processing.wakeUpAllWaiters();
return;
}
final Thread acceptorProblemListener =
new Thread("Acceptor problem listener") {
public void run() {
while (true) {
final Exception exception = m_acceptor.getPendingException();
if (exception == null) {
// Acceptor is shutting down.
break;
}
m_errorHandler.handleException(exception);
}
}
};
acceptorProblemListener.setDaemon(true);
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.
*/
@PreDestroy
public void shutdown() {
m_shutdown.set(true);
m_processing.set(false);
reset();
}
/**
* Wait to receive a message, then process it.
*
* @return true
if we processed a message successfully;
* false
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);
}
}
}
}
/**
* The number of connections that have been accepted and are still active.
* Used by the unit tests.
*
* @return The number of accepted connections.
*/
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).
*
* Any errors that occur will be handled with the error handler.
*
* @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).
*
*
* Any errors that occur will be handled with the error handler.
*
*
* @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));
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy