com.sshtools.ssh.message.SshMessageRouter Maven / Gradle / Ivy
/**
* Copyright 2003-2016 SSHTOOLS Limited. All Rights Reserved.
*
* For product documentation visit https://www.sshtools.com/
*
* This file is part of J2SSH Maverick.
*
* J2SSH Maverick is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* J2SSH Maverick is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with J2SSH Maverick. If not, see .
*/
package com.sshtools.ssh.message;
import java.io.IOException;
import java.util.Vector;
import com.sshtools.logging.Log;
import com.sshtools.ssh.SshException;
import com.sshtools.ssh.SshIOException;
/**
*
* This abstract class provides a synchronized message routing framework. The
* protocol implementation supplies a message reader interface, to which only
* one thread is allowed access at any one time, threads requiring a message
* whilst another thread is blocking are queued to await notification of when
* the reader is available. Since a message read by one thread may be destined
* for another the router takes charge of this before notifying other queued
* threads that the block is available. When they receive this notification they
* first check their own message stores before requesting the block again.
*
*
* @author Lee David Painter
*/
public abstract class SshMessageRouter {
private SshAbstractChannel[] channels;
SshMessageReader reader;
SshMessageStore global;
ThreadSynchronizer sync;
private int count = 0;
boolean buffered;
MessagePump messagePump;
boolean isClosing = false;
Vector activeChannels = new Vector();
Vector shutdownHooks = new Vector();
boolean verbose = Boolean.valueOf(
System.getProperty("maverick.verbose", "false")).booleanValue();
public SshMessageRouter(SshMessageReader reader, int maxChannels,
boolean buffered) {
this.reader = reader;
this.buffered = buffered;
this.channels = new SshAbstractChannel[maxChannels];
this.global = new SshMessageStore(this, null, new MessageObserver() {
public boolean wantsNotification(Message msg) {
return false;
}
});
sync = new ThreadSynchronizer(buffered);
if (buffered) {
messagePump = new MessagePump();
sync.blockingThread = messagePump;
// J2SE messagePump.setDaemon(true);
}
}
public void start() {
if (Log.isDebugEnabled()) {
if (verbose) {
Log.debug(this, "starting message pump");
}
}
if (messagePump != null && !messagePump.isRunning()) {
String prefix = "";
String sourceThread = Thread.currentThread().getName();
if (sourceThread.indexOf('-') > -1) {
prefix = sourceThread.substring(0,
1 + sourceThread.indexOf('-'));
// retrieve an event Listener
// pass the event to the listener to process
}
messagePump
.setName(prefix + "MessagePump_" + messagePump.getName());
messagePump.start();
if (Log.isDebugEnabled()) {
if (verbose) {
Log.debug(this, "message pump started thread name:"
+ messagePump.getName());
}
}
}
}
public void addShutdownHook(Runnable r) {
if (r != null)
shutdownHooks.addElement(r);
}
public boolean isBuffered() {
return buffered;
}
public void stop() {
signalClosingState();
if (messagePump != null)
messagePump.stopThread();
if (shutdownHooks != null) {
for (int i = 0; i < shutdownHooks.size(); i++) {
try {
((Runnable) shutdownHooks.elementAt(i)).run();
} catch (Throwable t) {
}
}
}
}
public void signalClosingState() {
if (buffered && messagePump != null) {
synchronized (messagePump) {
isClosing = true;
}
}
}
protected SshMessageStore getGlobalMessages() {
return global;
}
public int getMaxChannels() {
return channels.length;
}
protected int allocateChannel(SshAbstractChannel channel) {
synchronized (channels) {
for (int i = 0; i < channels.length; i++) {
if (channels[i] == null) {
channels[i] = channel;
activeChannels.addElement(channel);
count++;
if (Log.isDebugEnabled()) {
Log.debug(this, "Allocated channel " + i);
}
return i;
}
}
return -1;
}
}
protected void freeChannel(SshAbstractChannel channel) {
synchronized (channels) {
if (channels[channel.getChannelId()] != null) {
if (channel.equals(channels[channel.getChannelId()])) {
channels[channel.getChannelId()] = null;
activeChannels.removeElement(channel);
count--;
if (Log.isDebugEnabled()) {
Log.debug(this,
"Freed channel " + channel.getChannelId());
}
}
}
}
}
protected SshAbstractChannel[] getActiveChannels() {
return (SshAbstractChannel[]) activeChannels
.toArray(new SshAbstractChannel[0]);
}
protected int maximumChannels() {
return channels.length;
}
public int getChannelCount() {
return count;
}
protected SshMessage nextMessage(SshAbstractChannel channel,
MessageObserver observer, long timeout) throws SshException,
InterruptedException {
long startTime = System.currentTimeMillis();
SshMessageStore store = channel == null ? global : channel
.getMessageStore();
if (Log.isDebugEnabled()) {
if (verbose) {
Log.debug(this, "using "
+ (channel == null ? "global store" : "channel store"));
}
}
MessageHolder holder = new MessageHolder();
while (holder.msg == null
&& (timeout == 0 || System.currentTimeMillis() - startTime < timeout)) {
/**
* There are no messages for this caller. First check the buffered
* state and look for possible errors from the buffer thread
*/
if (buffered && messagePump != null) {
if (Log.isDebugEnabled()) {
if (verbose) {
Log.debug(this, "waiting for messagePump lock");
}
}
synchronized (messagePump) {
if (!isClosing) {
if (messagePump.lastError != null) {
Throwable tmpEx = messagePump.lastError;
messagePump.lastError = null;
if (tmpEx instanceof SshException) {
if (Log.isDebugEnabled()) {
Log.debug(this,
"messagePump has SshException this will be caught by customer code");
}
throw (SshException) tmpEx;
} else if (tmpEx instanceof SshIOException) {
if (Log.isDebugEnabled()) {
Log.debug(this,
"messagePump has SshIOException this will be caught by customer code");
}
throw ((SshIOException) tmpEx)
.getRealException();
} else {
if (Log.isDebugEnabled()) {
Log.debug(this,
"messagePump has some other exception this will be caught by customer code");
}
throw new SshException(tmpEx);
}
}
}
}
}
/**
* Request a block on the message reader
*/
if (sync.requestBlock(store, observer, holder)) {
try {
if (Log.isDebugEnabled()) {
if (verbose) {
Log.debug(this, "block for message");
}
}
blockForMessage();
} finally {
// Release the block so that other threads may block or
// return with the
// newly arrived message
sync.releaseBlock();
}
}
}
if (holder.msg == null) {
if (Log.isDebugEnabled()) {
Log.debug(this, "Mesage timeout reached timeout="
+ timeout);
}
throw new SshException(
"The message was not received before the specified timeout period timeout="
+ timeout, SshException.MESSAGE_TIMEOUT);
}
return (SshMessage) holder.msg;
}
public boolean isBlockingThread(Thread thread) {
return sync.isBlockOwner(thread);
}
private void blockForMessage() throws SshException {
// Read and create a message
SshMessage message = createMessage(reader.nextMessage());
if (Log.isDebugEnabled()) {
if (verbose) {
Log.debug(this, "read next message");
}
}
// Determine the destination channel (if any)
SshAbstractChannel destination = null;
if (message instanceof SshChannelMessage) {
destination = channels[((SshChannelMessage) message).getChannelId()];
}
// Call the destination so that they may process the message
boolean processed = destination == null ? processGlobalMessage(message)
: destination
.processChannelMessage((SshChannelMessage) message);
// If the previous call did not process the message then add to the
// destinations message store
if (!processed) {
SshMessageStore ms = destination == null ? global : destination
.getMessageStore();
// add new message to message stores linked list.
ms.addMessage(message);
}
}
/**
* Called when the threaded router closes.
*/
protected abstract void onThreadExit();
/**
*
* Called by the message routing framework to request the creation of an
* {@link SshMessage}.
*
*
* @param messageid
* @return the new message instance
*/
protected abstract SshMessage createMessage(byte[] msg) throws SshException;
/**
*
* Called by the message routing framework so that the routing
* implementation may process a global message. If the message is processed
* and no further action is required this method should return
* true
, if the method returns false
then the
* message will be added to the global message store.
*
*
* @param msg
* @return true
if the message was processed, otherwise
* false
* @throws IOException
*/
protected abstract boolean processGlobalMessage(SshMessage msg)
throws SshException;
class MessagePump extends Thread {
Throwable lastError;
boolean running = false;
public void run() {
try {
running = true;
while (running) {
try {
blockForMessage();
// We have a message so release waiting threads
sync.releaseWaiting();
} catch (Throwable t) {
synchronized (MessagePump.this) {
// If were not closing then save this error
if (!isClosing) {
Log.info(
this,
"Message pump caught exception: "
+ t.getMessage());
lastError = t;
}
stopThread();
}
}
}
// Finally release the block as we exit
sync.releaseBlock();
} finally {
onThreadExit();
}
}
public void stopThread() {
running = false;
if (!Thread.currentThread().equals(this))
interrupt();
}
public boolean isRunning() {
return running;
}
}
}