/*
* Copyright (C) 2006-2008 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.jlan.smb.server.nio;
import java.io.IOException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Vector;
import org.alfresco.jlan.debug.Debug;
import org.alfresco.jlan.server.SrvSessionQueue;
import org.alfresco.jlan.server.thread.ThreadRequest;
import org.alfresco.jlan.server.thread.ThreadRequestPool;
import org.alfresco.jlan.smb.server.SMBSrvSession;
import org.alfresco.jlan.smb.server.SMBSrvSessionState;
/**
* CIFS Request Handler Class
*
* <p>Handles the receiving of CIFS requests for a number of CIFS sessions.
*
* @author gkspencer
*/
public class CIFSRequestHandler extends RequestHandler implements Runnable {
// Request handler index, used to generate the thread name
private static int _handlerId;
// Selector used to monitor a group of socket channels for incoming requests
private Selector m_selector;
// Thread that the request handler runs in
private Thread m_thread;
// Thread pool for processing requests
private ThreadRequestPool m_threadPool;
// Queue of sessions that are pending setup with the selector
private SrvSessionQueue m_sessQueue;
// Client socket session timeout
private int m_clientSocketTimeout;
// Shutdown request flag
private boolean m_shutdown;
/**
* Class constructor
*
* @param threadPool ThreadRequestPool
* @param maxSess int
* @param sockTmo int
* @param debug boolean
*/
public CIFSRequestHandler( ThreadRequestPool threadPool, int maxSess, int sockTmo, boolean debug) {
super( maxSess);
// Set the thread pool to use for request processing
m_threadPool = threadPool;
// Set the client socket timeout
m_clientSocketTimeout = sockTmo;
// Create the session queue
m_sessQueue = new SrvSessionQueue();
// Set the debug output enable
setDebug( debug);
// Start the request handler in a seperate thread
m_thread = new Thread( this);
m_thread.setName( "CIFSRequestHandler_" + ++_handlerId);
m_thread.setDaemon( false);
m_thread.start();
}
/**
* Return the current session count
*
* @return int
*/
public final int getCurrentSessionCount() {
int sessCnt = 0;
if ( m_selector != null)
sessCnt = m_selector.keys().size();
return sessCnt;
}
/**
* Check if this request handler has free session slots available
*
* @return boolean
*/
public final boolean hasFreeSessionSlot() {
return ( getCurrentSessionCount() + m_sessQueue.numberOfSessions()) < getMaximumSessionCount() ? true : false;
}
/**
* Return the client socket timeout, in milliseconds
*
* @return int
*/
public final int getSocketTimeout() {
return m_clientSocketTimeout;
}
/**
* Set the client socket timeout, in milliseconds
*
* @param tmo int
*/
public final void setSocketTimeout(int tmo) {
m_clientSocketTimeout = tmo;
}
/**
* Queue a new session to the request handler, wakeup the request handler thread to register it with the
* selector.
*
* @param sess SMBSrvSession
*/
public final void queueSessionToHandler( SMBSrvSession sess) {
// Add the new session to the pending queue
m_sessQueue.addSession( sess);
// Wakeup the main thread to process the new session queue
if ( m_selector != null)
m_selector.wakeup();
}
/**
* Return the request handler name
*
* @return String
*/
public final String getName() {
if ( m_thread != null)
return m_thread.getName();
return "CIFSRequestHandler";
}
/**
* Enable/disable thread pool debugging
*
* @param dbg boolean
*/
public final void setThreadDebug( boolean dbg) {
m_threadPool.setDebug( dbg);
}
/**
* Run the main processing in a seperate thread
*/
public void run() {
// Clear the shutdown flag, may have been restarted
m_shutdown = false;
// Initialize the socket selector
try {
// Create the selector
m_selector = Selector.open();
}
catch ( IOException ex) {
// DEBUG
if ( Debug.EnableInfo && hasDebug()) {
Debug.println( "[SMB] Error opening/registering Selector");
Debug.println( ex);
}
m_shutdown = true;
}
// Loop until shutdown
Vector<ThreadRequest> reqList = new Vector<ThreadRequest>();
while ( m_shutdown == false) {
// Check if there are any sessions registered
int sessCnt = 0;
if ( m_selector.keys().size() == 0) {
// Indicate that this request handler has no active sessions
fireRequestHandlerEmptyEvent();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println( "[SMB] Request handler " + m_thread.getName() + " waiting for session ...");
// Wait for a session to be added to the handler
try {
m_sessQueue.waitWhileEmpty();
}
catch ( InterruptedException ex) {
}
}
else {
// Wait for client requests
try {
sessCnt = m_selector.select();
}
catch ( CancelledKeyException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug() && m_shutdown == false) {
Debug.println( "[SMB] Request handler error waiting for events");
Debug.println(ex);
}
}
catch ( IOException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug()) {
Debug.println( "[SMB] Request handler error waiting for events");
Debug.println(ex);
}
}
}
// Check if the shutdown flag has been set
if ( m_shutdown == true)
continue;
// Check if there are any events to process
if ( sessCnt > 0) {
// DEBUG
// if ( Debug.EnableInfo && hasDebug()) // && sessCnt > 1)
// Debug.println( "[SMB] Request handler " + m_thread.getName() + " session events, sessCnt=" + sessCnt + "/" + m_selector.keys().size());
// Clear the thread request list
reqList.clear();
// Iterate the selected keys
Iterator<SelectionKey> keysIter = m_selector.selectedKeys().iterator();
long timeNow = System.currentTimeMillis();
while ( keysIter.hasNext()) {
// Get the current selection key and check if has an incoming request
SelectionKey selKey = keysIter.next();
keysIter.remove();
if ( selKey.isValid() == false) {
// Remove the selection key
Debug.println("CIFSRequestHandler: Cancelling selection key - " + selKey);
selKey.cancel();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println( "[SMB] NIO Selection key not valid, sess=" + selKey.attachment());
}
else if ( selKey.isReadable()) {
// DEBUG
// if ( Debug.EnableInfo && hasDebug())
// Debug.println("[SMB] Socket read event");
// Switch off read events for this channel until the current processing is complete
selKey.interestOps( selKey.interestOps() & ~SelectionKey.OP_READ);
// Get the associated session and queue a request to the thread pool to read and process the CIFS request
SMBSrvSession sess = (SMBSrvSession) selKey.attachment();
reqList.add( new NIOCIFSThreadRequest( sess, selKey));
// Update the last I/O time for the session
sess.setLastIOTime( timeNow);
// Check if there are enough thread requests to be queued
if ( reqList.size() >= 5) {
// DEBUG
// if ( Debug.EnableInfo && hasDebug())
// Debug.println( "[SMB] Queueing " + reqList.size() + " thread requests");
// Queue the requests to the thread pool
m_threadPool.queueRequests( reqList);
reqList.clear();
}
}
else if ( selKey.isValid() == false) {
// Remove the selection key
Debug.println("CIFSRequestHandler: Cancelling selection key - " + selKey);
selKey.cancel();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println( "[SMB] NIO Selection key not valid, sess=" + selKey.attachment());
}
else {
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[SMB] Unprocessed selection key, " + selKey);
}
}
// Queue the thread requests
if ( reqList.size() > 0) {
// DEBUG
// if ( Debug.EnableInfo && hasDebug()) // && reqList.size() > 1)
// Debug.println( "[SMB] Queueing " + reqList.size() + " thread requests (last)");
// Queue the requests to the thread pool
m_threadPool.queueRequests( reqList);
reqList.clear();
}
}
// Check if there are any new sessions that need to be registered with the selector, or sessions to be removed
if ( m_sessQueue.numberOfSessions() > 0) {
// Register the new sessions with the selector
while ( m_sessQueue.numberOfSessions() > 0) {
// Get a new session from the queue
SMBSrvSession sess = (SMBSrvSession) m_sessQueue.removeSessionNoWait();
if ( sess != null) {
// check the session state, if the session is in a setup state it is a new session
if ( sess.getState() <= SMBSrvSessionState.SMBNEGOTIATE) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println( "[SMB] Register session with request handler, handler=" + m_thread.getName() + ", sess=" + sess.getUniqueId());
// Get the socket channel from the sessions packet handler
if ( sess.getPacketHandler() instanceof ChannelPacketHandler) {
// Get the channel packet handler and register the socket channel with the selector
ChannelPacketHandler chanPktHandler = (ChannelPacketHandler) sess.getPacketHandler();
SocketChannel sessChannel = chanPktHandler.getSocketChannel();
try {
// Register the session channel with the selector
sessChannel.configureBlocking( false);
sessChannel.register( m_selector, SelectionKey.OP_READ, sess);
}
catch ( ClosedChannelException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println( "[SMB] Failed to register session channel, closed channel");
}
catch ( IOException ex) {
// DEBUG
if ( Debug.EnableError && hasDebug())
Debug.println( "[SMB] Failed to set channel blocking mode, " + ex.getMessage());
}
}
}
else {
// Remove the session
// TODO:
}
}
}
}
}
// Close all sessions
if ( m_selector != null) {
// Enumerate the selector keys to get the session list
Iterator<SelectionKey> selKeys = m_selector.keys().iterator();
while ( selKeys.hasNext()) {
// Get the current session via the selection key
SelectionKey curKey = selKeys.next();
SMBSrvSession sess = (SMBSrvSession) curKey.attachment();
// Close the session
sess.closeSession();
}
// Close the selector
try {
m_selector.close();
}
catch ( IOException ex) {
}
}
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println( "[SMB] Closed CIFS request handler, " + m_thread.getName());
}
/**
* Close the request handler
*/
public final void closeHandler() {
// Check if the thread is running
if ( m_thread != null) {
m_shutdown = true;
try {
m_thread.interrupt();
if ( m_selector != null)
m_selector.wakeup();
}
catch (Exception ex) {
}
}
}
/**
* Check for idle sessions
*
* @return int
*/
protected final int checkForIdleSessions() {
// Check if the request handler has any active sessions
int idleCnt = 0;
if ( m_selector != null && m_selector.keys().size() > 0) {
// Time to check
long checkTime = System.currentTimeMillis() - (long) m_clientSocketTimeout;
// Enumerate the selector keys to get the session list
Iterator<SelectionKey> selKeys = m_selector.keys().iterator();
while ( selKeys.hasNext()) {
// Get the current session via the selection key
SelectionKey curKey = selKeys.next();
SMBSrvSession sess = (SMBSrvSession) curKey.attachment();
// Check the time of the last I/O request on this session
if ( sess != null && sess.getLastIOTime() < checkTime) {
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println( "[SMB] Closing idle session, " + sess.getUniqueId() + ", addr=" + sess.getRemoteAddress().getHostAddress());
// Close the session
sess.closeSession();
sess.processPacket( null);
// Update the idle session count
idleCnt++;
}
}
// If any sessions were closed then wakeup the selector thread
if ( idleCnt > 0)
m_selector.wakeup();
}
// Return the count of idle sessions that were closed
return idleCnt;
}
}