/* * Copyright 1999-2006 University of Chicago * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.dcache.ftp.client.dc; import java.net.ServerSocket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.dcache.ftp.client.DataSink; import org.dcache.ftp.client.DataSource; import org.dcache.ftp.client.GridFTPSession; import org.dcache.ftp.client.HostPort; import org.dcache.ftp.client.exception.ServerException; import org.dcache.ftp.client.extended.GridFTPServerFacade; import org.dcache.ftp.client.vanilla.BasicServerControlChannel; import org.dcache.ftp.client.vanilla.FTPServerFacade; public class TransferThreadManager { static final Logger logger = LoggerFactory.getLogger(TransferThreadManager.class); protected final SocketPool socketPool; protected final GridFTPServerFacade facade; protected final BasicServerControlChannel localControlChannel; protected final GridFTPSession gSession; protected TaskThread taskThread; protected int transferThreadCount = 0; protected final DataChannelFactory dataChannelFactory; public TransferThreadManager(SocketPool socketPool, GridFTPServerFacade facade, BasicServerControlChannel myControlChannel, GridFTPSession gSession) { this.socketPool = socketPool; this.facade = facade; this.gSession = gSession; this.localControlChannel = myControlChannel; this.dataChannelFactory = new GridFTPDataChannelFactory(); } /** * Act as the active side. Connect to the server and * store the newly connected sockets in the socketPool. */ public void activeConnect(HostPort hp, int connections) { for (int i = 0; i < connections; i++) { SocketBox sbox = new ManagedSocketBox(); logger.debug("adding new empty socketBox to the socket pool"); socketPool.add(sbox); logger.debug( "connecting active socket " + i + "; total cached sockets = " + socketPool.count()); Task task = new GridFTPActiveConnectTask( hp, localControlChannel, sbox, gSession); runTask(task); } } /** * use only in mode E */ public void activeClose(TransferContext context, int connections) { try { //this could be improved; for symmetry and performance, //make it a separate task class and pass to the taskThread for (int i = 0; i < connections; i++) { SocketBox sbox = socketPool.checkOut(); GridFTPDataChannel dc = new GridFTPDataChannel(gSession, sbox); EBlockImageDCWriter writer = (EBlockImageDCWriter) dc.getDataChannelSink(context); writer.setDataStream(sbox.getSocket().getOutputStream()); // close the socket writer.close(); // do not reuse the socket socketPool.remove(sbox); sbox.setSocket(null); } } catch (Exception e) { FTPServerFacade.exceptionToControlChannel( e, "closing of a reused connection failed", localControlChannel); } } /** * This should be used once the remote active server connected to us. * This method starts transfer threads that will * read data from the source and send. * * @param reusable if set to false, the sockets will not be reused after * the transfer */ public synchronized void startTransfer(DataSource source, TransferContext context, int connections, boolean reusable) throws ServerException { // things would get messed up if more than 1 file was transfered // simultaneously with the same transfer manager if (transferThreadCount != 0) { throw new ServerException( ServerException.PREVIOUS_TRANSFER_ACTIVE); } for (int i = 0; i < connections; i++) { logger.debug( "checking out a socket; total cached sockets = " + socketPool.count() + "; free = " + socketPool.countFree() + "; busy = " + socketPool.countBusy()); SocketBox sbox = socketPool.checkOut(); if (sbox == null) { logger.debug("No free sockets available, aborting."); return; } ((ManagedSocketBox) sbox).setReusable(reusable); Task task = new ActiveStartTransferTask(source, localControlChannel, sbox, gSession, dataChannelFactory, context); runTask(task); } } /** * This should be used once the remote active server connected to us. * This method starts transfer threads that will * receive the data and store them in the sink. * Because of transfer direction, this method cannot be used with EBLOCK. * Therefore the number of connections is fixed at 1. * * @param reusable if set to false, the sockets will not be reused after * the transfer */ public synchronized void startTransfer(DataSink sink, TransferContext context, int connections, boolean reusable) throws ServerException { // things would get messed up if more than 1 file was transfered // simultaneously with the same transfer manager if (transferThreadCount != 0) { throw new ServerException( ServerException.PREVIOUS_TRANSFER_ACTIVE); } for (int i = 0; i < connections; i++) { logger.debug( "checking out a socket; total cached sockets = " + socketPool.count() + "; free = " + socketPool.countFree() + "; busy = " + socketPool.countBusy()); SocketBox sbox = socketPool.checkOut(); if (sbox == null) { logger.debug("No free sockets available, aborting."); return; } ((ManagedSocketBox) sbox).setReusable(reusable); Task task = new ActiveStartTransferTask( sink, localControlChannel, sbox, gSession, dataChannelFactory, context); runTask(task); } } /** * Accept connections from the remote server, * and start transfer threads that will read incoming data and store * in the sink. * * @param connections the number of expected connections */ public synchronized void passiveConnect(DataSink sink, TransferContext context, int connections, ServerSocket serverSocket) throws ServerException { // things would get messed up if more than 1 file was transfered // simultaneously with the same transfer manager if (transferThreadCount != 0) { throw new ServerException( ServerException.PREVIOUS_TRANSFER_ACTIVE); } for (int i = 0; i < connections; i++) { Task task = new GridFTPPassiveConnectTask( serverSocket, sink, localControlChannel, gSession, dataChannelFactory, (EBlockParallelTransferContext) context); runTask(task); } } /** * Accept connection from the remote server * and start transfer thread that will read incoming data and store in * the sink. This method, because of direction of transfer, cannot be * used with EBlock. Therefore it is fixed to create only 1 connection. */ public synchronized void passiveConnect(DataSource source, TransferContext context, ServerSocket serverSocket) throws ServerException { // things would get messed up if more than 1 file was transfered // simultaneously with the same transfer manager if (transferThreadCount != 0) { throw new ServerException( ServerException.PREVIOUS_TRANSFER_ACTIVE); } Task task = new GridFTPPassiveConnectTask( serverSocket, source, localControlChannel, gSession, dataChannelFactory, (EBlockParallelTransferContext) context); runTask(task); } public synchronized int getTransferThreadCount() { return transferThreadCount; } public synchronized void transferThreadStarting() { transferThreadCount++; logger.debug("one transfer started, total active = " + transferThreadCount); } public synchronized void transferThreadTerminating() { transferThreadCount--; logger.debug("one transfer terminated, total active = " + transferThreadCount); } /** * Use this as an interface to the local manager thread. * This submits the task to the thread queue. * The thread will perform it when it's ready with other * waiting tasks. **/ private synchronized void runTask(Task task) { if (taskThread == null) { taskThread = new TaskThread(); } taskThread.runTask(task); } public synchronized void stopTaskThread() { if (taskThread != null) { taskThread.stop(); taskThread.join(); taskThread = null; } } public void close() { stopTaskThread(); } }