All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sshtools.ssh.message.SshMessageRouter Maven / Gradle / Ivy

The newest version!
/**
 * 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; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy