org.dcache.xrootd.door.XrootdDoor.java Source code

Java tutorial

Introduction

Here is the source code for org.dcache.xrootd.door.XrootdDoor.java

Source

/**
 * Copyright (C) 2012 dCache.org <support@dcache.org>
 *
 * This file is part of xrootd4j-backport.
 *
 * xrootd4j-backport is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * xrootd4j-backport 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
 * Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with xrootd4j-backport.  If not, see
 * <http://www.gnu.org/licenses/>.
 */
package org.dcache.xrootd.door;

import java.io.PrintWriter;

import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Set;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.security.auth.Subject;

import com.google.common.base.Splitter;
import com.google.common.collect.Ranges;
import org.dcache.vehicles.PnfsListDirectoryMessage;
import org.dcache.vehicles.XrootdDoorAdressInfoMessage;
import org.dcache.vehicles.XrootdProtocolInfo;
import org.dcache.namespace.FileAttribute;
import org.dcache.namespace.FileType;
import org.dcache.util.Transfer;
import org.dcache.util.TransferRetryPolicy;
import org.dcache.util.TransferRetryPolicies;
import org.dcache.util.PingMoversTask;
import org.dcache.util.FireAndForgetTask;
import org.dcache.cells.AbstractCellComponent;
import org.dcache.cells.CellMessageReceiver;
import org.dcache.cells.CellCommandListener;
import org.dcache.cells.CellStub;
import org.dcache.cells.MessageCallback;
import diskCacheV111.movers.NetIFContainer;
import diskCacheV111.util.CacheException;
import diskCacheV111.util.PermissionDeniedCacheException;
import diskCacheV111.util.FileMetaData;
import diskCacheV111.util.PnfsHandler;
import diskCacheV111.util.FsPath;
import diskCacheV111.vehicles.DoorTransferFinishedMessage;
import diskCacheV111.vehicles.IoDoorInfo;
import diskCacheV111.vehicles.IoDoorEntry;
import diskCacheV111.vehicles.PoolIoFileMessage;
import diskCacheV111.vehicles.PoolMoverKillMessage;
import dmg.cells.nucleus.CellVersion;
import dmg.cells.nucleus.CellPath;
import dmg.cells.nucleus.NoRouteToCellException;
import dmg.cells.services.login.LoginManagerChildrenInfo;
import dmg.util.Args;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;

/**
 * Shared cell component used to interface with the rest of
 * dCache.
 *
 * Current implementation is more or less a copy of the old xrootd
 * code. Should be replaced by the equivalent component developed by
 * Tatjana and Tigran.
 */
public class XrootdDoor extends AbstractCellComponent implements CellMessageReceiver, CellCommandListener {
    public final static String XROOTD_PROTOCOL_STRING = "Xrootd";
    public final static int XROOTD_PROTOCOL_MAJOR_VERSION = 2;
    public final static int XROOTD_PROTOCOL_MINOR_VERSION = 7;
    public final static String XROOTD_PROTOCOL_VERSION = String.format("%d.%d", XROOTD_PROTOCOL_MAJOR_VERSION,
            XROOTD_PROTOCOL_MINOR_VERSION);

    private final static Logger _log = LoggerFactory.getLogger(XrootdDoor.class);

    private final static AtomicInteger _handleCounter = new AtomicInteger();

    private final static long PING_DELAY = 300000;

    private final static TransferRetryPolicy RETRY_POLICY = TransferRetryPolicies.tryOncePolicy(Long.MAX_VALUE);

    private String _cellName;
    private String _domainName;

    private List<FsPath> _readPaths = Collections.singletonList(new FsPath());
    private List<FsPath> _writePaths = Collections.singletonList(new FsPath());

    private CellStub _poolStub;
    private CellStub _poolManagerStub;
    private CellStub _billingStub;

    private int _moverTimeout = 180000;

    private PnfsHandler _pnfs;

    private String _ioQueue;

    private Map<UUID, DirlistRequestHandler> _requestHandlers = new ConcurrentHashMap<UUID, DirlistRequestHandler>();

    private ScheduledExecutorService _dirlistTimeoutExecutor;

    /**
     * Current xrootd transfers. The key is the xrootd file handle.
     */
    private final Map<Integer, XrootdTransfer> _transfers = new ConcurrentHashMap<Integer, XrootdTransfer>();

    public static CellVersion getStaticCellVersion() {
        return new CellVersion(diskCacheV111.util.Version.getVersion(), "$Revision: 11646 $");
    }

    @Required
    public void setPoolStub(CellStub stub) {
        _poolStub = stub;
    }

    @Required
    public void setPoolManagerStub(CellStub stub) {
        _poolManagerStub = stub;
    }

    @Required
    public void setBillingStub(CellStub stub) {
        _billingStub = stub;
    }

    /**
     * Converts a colon separated list of paths to a List of FsPath.
     */
    private List<FsPath> toFsPaths(String s) {
        List<FsPath> list = new ArrayList();
        for (String path : Splitter.on(":").omitEmptyStrings().split(s)) {
            list.add(new FsPath(path));
        }
        return list;
    }

    /**
     * The list of paths which are authorized for xrootd write access.
     */
    @Required
    public void setWritePaths(String s) {
        _writePaths = toFsPaths(s);
    }

    /**
     * Returns the list of write paths.
     *
     * Notice that the getter uses a different property name than the
     * setter. This is because the getter returns a different type
     * than set by the setter, and hence we must not use the same
     * property name (otherwise Spring complains).
     */
    @Required
    public List<FsPath> getWritePathsList() {
        return _writePaths;
    }

    /**
     * The list of paths which are authorized for xrootd write access.
     */
    @Required
    public void setReadPaths(String s) {
        _readPaths = toFsPaths(s);
    }

    /**
     * Returns the list of read paths.
     *
     * Notice that the getter uses a different property name than the
     * setter. This is because the getter returns a different type
     * than set by the setter, and hence we must not use the same
     * property name (otherwise Spring complains).
     */
    public List<FsPath> getReadPathsList() {
        return _readPaths;
    }

    @Required
    public void setPnfsHandler(PnfsHandler pnfs) {
        _pnfs = pnfs;
    }

    /**
     * The actual mover queue on the pool onto which this request gets
     * scheduled.
     */
    @Required
    public void setIoQueue(String ioQueue) {
        _ioQueue = ioQueue;
    }

    public String getIoQueue() {
        return _ioQueue;
    }

    /**
     * Returns the mover timeout in milliseconds.
     */
    public int getMoverTimeout() {
        return _moverTimeout;
    }

    /**
     * The mover timeout is the time we wait for the mover to start
     * after having been enqueued.
     *
     * @param timeout The mover timeout in milliseconds
     */
    @Required
    public void setMoverTimeout(int timeout) {
        if (timeout <= 0) {
            throw new IllegalArgumentException("Timeout must be positive");
        }
        _moverTimeout = timeout;
    }

    /**
     * Sets the ScheduledExecutorService used for periodic tasks.
     */
    @Required
    public void setExecutor(ScheduledExecutorService executor) {
        executor.scheduleAtFixedRate(new FireAndForgetTask(new PingMoversTask(_transfers.values())), PING_DELAY,
                PING_DELAY, TimeUnit.MILLISECONDS);
    }

    @Required
    public void setDirlistTimeoutExecutor(ScheduledExecutorService executor) {
        _dirlistTimeoutExecutor = executor;
    }

    /**
     * Performs component initialization. Must be called after all
     * dependencies have been injected.
     */
    public void init() {
        _cellName = getCellName();
        _domainName = getCellDomainName();
    }

    @Override
    public void getInfo(PrintWriter pw) {
        pw.println(String.format("Protocol Version %d.%d", XROOTD_PROTOCOL_MAJOR_VERSION,
                XROOTD_PROTOCOL_MINOR_VERSION));
    }

    private XrootdTransfer createTransfer(InetSocketAddress client, FsPath path, UUID uuid, InetSocketAddress local,
            Subject subject) {
        XrootdTransfer transfer = new XrootdTransfer(_pnfs, subject, path) {
            @Override
            public synchronized void finished(CacheException error) {
                super.finished(error);

                _transfers.remove(getFileHandle());

                if (error == null) {
                    notifyBilling(0, "");
                    _log.info("Transfer {}@{} finished", getPnfsId(), getPool());
                } else {
                    int rc = error.getRc();
                    String message = error.getMessage();
                    notifyBilling(rc, message);
                    _log.info("Transfer {}@{} failed: {} (error code={})",
                            new Object[] { getPnfsId(), getPool(), message, rc });
                }
            }
        };
        transfer.setCellName(_cellName);
        transfer.setDomainName(_domainName);
        transfer.setPoolManagerStub(_poolManagerStub);
        transfer.setPoolStub(_poolStub);
        transfer.setBillingStub(_billingStub);
        transfer.setClientAddress(client);
        transfer.setUUID(uuid);
        transfer.setDoorAddress(local);
        transfer.setFileHandle(_handleCounter.getAndIncrement());
        return transfer;
    }

    public XrootdTransfer read(InetSocketAddress client, FsPath path, UUID uuid, InetSocketAddress local,
            Subject subject) throws CacheException, InterruptedException {
        if (!isReadAllowed(path)) {
            throw new PermissionDeniedCacheException("Write permission denied");
        }

        XrootdTransfer transfer = createTransfer(client, path, uuid, local, subject);
        int handle = transfer.getFileHandle();

        InetSocketAddress address = null;
        _transfers.put(handle, transfer);
        try {
            transfer.readNameSpaceEntry();
            transfer.selectPoolAndStartMover(_ioQueue, RETRY_POLICY);
            address = transfer.waitForRedirect(_moverTimeout);
            if (address == null) {
                throw new CacheException(transfer.getPool() + " failed to open TCP socket");
            }

            transfer.setStatus("Mover " + transfer.getPool() + "/" + transfer.getMoverId() + ": Sending");
        } catch (CacheException e) {
            transfer.notifyBilling(e.getRc(), e.getMessage());
            throw e;
        } catch (InterruptedException e) {
            transfer.notifyBilling(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, "Transfer interrupted");
            throw e;
        } catch (RuntimeException e) {
            transfer.notifyBilling(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.toString());
            throw e;
        } finally {
            if (address == null) {
                transfer.killMover(0);
                _transfers.remove(handle);
            }
        }
        return transfer;
    }

    public XrootdTransfer write(InetSocketAddress client, FsPath path, UUID uuid, boolean createDir,
            boolean overwrite, InetSocketAddress local, Subject subject)
            throws CacheException, InterruptedException {
        if (!isWriteAllowed(path)) {
            throw new PermissionDeniedCacheException("Write permission denied");
        }

        XrootdTransfer transfer = createTransfer(client, path, uuid, local, subject);
        transfer.setOverwriteAllowed(overwrite);
        int handle = transfer.getFileHandle();
        InetSocketAddress address = null;
        _transfers.put(handle, transfer);
        try {
            if (createDir) {
                transfer.createNameSpaceEntryWithParents();
            } else {
                transfer.createNameSpaceEntry();
            }
            try {
                transfer.selectPoolAndStartMover(_ioQueue, RETRY_POLICY);

                address = transfer.waitForRedirect(_moverTimeout);
                if (address == null) {
                    throw new CacheException(transfer.getPool() + " failed to open TCP socket");
                }

                transfer.setStatus("Mover " + transfer.getPool() + "/" + transfer.getMoverId() + ": Receiving");
            } finally {
                if (address == null) {
                    transfer.deleteNameSpaceEntry();
                }
            }
        } catch (CacheException e) {
            transfer.notifyBilling(e.getRc(), e.getMessage());
            throw e;
        } catch (InterruptedException e) {
            transfer.notifyBilling(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, "Transfer interrupted");
            throw e;
        } catch (RuntimeException e) {
            transfer.notifyBilling(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.toString());
            throw e;
        } finally {
            if (address == null) {
                _transfers.remove(handle);
            }
        }
        return transfer;
    }

    /**
     * Delete the file denoted by path from the namespace
     *
     * @param path The path of the file that is going to be deleted
     * @throws CacheException Deletion of the file failed
     * @throws PermissionDeniedCacheException Caller does not have permission to delete the file
     */
    public void deleteFile(FsPath path, Subject subject) throws PermissionDeniedCacheException, CacheException {
        PnfsHandler pnfsHandler = new PnfsHandler(_pnfs, subject);

        if (!isWriteAllowed(path)) {
            throw new PermissionDeniedCacheException("Write permission denied");
        }

        Set<FileType> allowedSet = EnumSet.of(FileType.REGULAR);
        pnfsHandler.deletePnfsEntry(path.toString(), allowedSet);
    }

    /**
     * Delete the directory denoted by path from the namespace
     *
     * @param path The path of the directory that is going to be deleted
     * @throws CacheException
     */
    public void deleteDirectory(FsPath path, Subject subject) throws CacheException {
        PnfsHandler pnfsHandler = new PnfsHandler(_pnfs, subject);

        if (!isWriteAllowed(path)) {
            throw new PermissionDeniedCacheException("Write permission denied");
        }

        Set<FileType> allowedSet = EnumSet.of(FileType.DIR);
        pnfsHandler.deletePnfsEntry(path.toString(), allowedSet);
    }

    /**
     * Create the directory denoted by path in the namespace.
     *
     * @param path The path of the directory that is going to be created.
     * @param createParents Indicates whether the parent directories of the
     *        directory should be created automatically if they do not yet
     *        exist.
     * @throws CacheException Creation of the directory failed.
     */
    public void createDirectory(FsPath path, boolean createParents, Subject subject) throws CacheException {
        PnfsHandler pnfsHandler = new PnfsHandler(_pnfs, subject);

        if (!isWriteAllowed(path)) {
            throw new PermissionDeniedCacheException("Write permission denied");
        }

        if (createParents) {
            pnfsHandler.createDirectories(path);
        } else {
            pnfsHandler.createPnfsDirectory(path.toString());
        }
    }

    /**
     * Emulate a file-move-operation by renaming sourcePath to targetPath in
     * the namespace
     * @param sourcePath the original path of the file that should be moved
     * @param targetPath the path to which the file should be moved
     * @throws CacheException
     */
    public void moveFile(FsPath sourcePath, FsPath targetPath, Subject subject) throws CacheException {
        PnfsHandler pnfsHandler = new PnfsHandler(_pnfs, subject);

        if (!isWriteAllowed(sourcePath)) {
            throw new PermissionDeniedCacheException("No write permission on" + " source path!");
        }

        if (!isWriteAllowed(targetPath)) {
            throw new PermissionDeniedCacheException("No write permission on" + " target path!");
        }

        pnfsHandler.renameEntry(sourcePath.toString(), targetPath.toString(), false);
    }

    /**
     * List the contents of a path, usually a directory. In order to make
     * fragmented responses, as supported by the xrootd protocol, possible and
     * not block the processing thread in the door, this will register the
     * passed callback along with the UUID of the message that is sent to
     * PNFS-manager.
     *
     * Once PNFS-manager replies to the message, that callback is retrieved and
     * the response is processed by the callback.
     *
     * @param path The path that is listed
     * @param subject Representation of user that request listing
     * @param callback The callback that will process the response
     */
    public void listPath(FsPath path, Subject subject, MessageCallback<PnfsListDirectoryMessage> callback) {
        PnfsHandler pnfsHandler = new PnfsHandler(_pnfs, subject);

        PnfsListDirectoryMessage msg = new PnfsListDirectoryMessage(path.toString(), null, Ranges.<Integer>all(),
                EnumSet.noneOf(FileAttribute.class));
        UUID uuid = msg.getUUID();

        try {
            DirlistRequestHandler requestHandler = new DirlistRequestHandler(uuid, pnfsHandler.getPnfsTimeout(),
                    callback);
            _requestHandlers.put(uuid, requestHandler);
            pnfsHandler.send(msg);
            requestHandler.resetTimeout();
        } catch (NoRouteToCellException e) {
            _requestHandlers.remove(uuid);
            callback.noroute(e.getDestinationPath());
        } catch (RejectedExecutionException ree) {
            _requestHandlers.remove(uuid);
            callback.failure(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, ree.getMessage());
        }
    }

    /**
     * Encapsulate the list directory callback into a handler that manages the
     * scheduled executor service for the timeout handling.
     *
     */
    private class DirlistRequestHandler {
        private ScheduledFuture<?> _executionInstance;
        private final long _timeout;
        private final UUID _uuid;
        private final MessageCallback<PnfsListDirectoryMessage> _callback;

        public DirlistRequestHandler(UUID uuid, long responseTimeout,
                MessageCallback<PnfsListDirectoryMessage> callback) {
            _uuid = uuid;
            _timeout = responseTimeout;
            _callback = callback;
        }

        /**
         * Final listing result. Report back via callback and cancel
         * the timeout handler.
         * @param msg The reply containing the listing result.
         */
        public synchronized void success(PnfsListDirectoryMessage msg) {
            if (_requestHandlers.remove(_uuid) == this) {
                cancelTimeout();
                _callback.setReply(msg);
                _callback.success();
            }
        }

        /**
         * Partial listing result, report that back to the callback. Also,
         * reset the timeout timer in anticipation of further listing results.
         * @param msg The reply containing the partial directory listing.
         */
        public synchronized void continueListing(PnfsListDirectoryMessage msg) {
            _callback.setReply(msg);
            try {
                _callback.success();
                resetTimeout();
            } catch (RejectedExecutionException ree) {
                _requestHandlers.remove(_uuid);
                _callback.failure(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, ree.getMessage());
            }
        }

        /**
         * Remove the request handler from the list, report a failure to the
         * callback and cancel the timeout timer.
         * @param msg The reply received from PNFS
         */
        public synchronized void failure(PnfsListDirectoryMessage msg) {
            if (_requestHandlers.remove(_uuid) == this) {
                cancelTimeout();
                _callback.setReply(msg);
                _callback.failure(msg.getReturnCode(), msg.getErrorObject());
            }
        }

        /**
         * Reschedule the timeout task with the same timeout as initially.
         * Rescheduling means cancelling the old task and submitting a new one.
         * @throws RejectedExecutionException
         */
        public synchronized void resetTimeout() throws RejectedExecutionException {
            Runnable target = new Runnable() {
                @Override
                public void run() {
                    if (_requestHandlers.remove(_uuid) == DirlistRequestHandler.this) {
                        _callback.timeout(null);
                    }
                }
            };

            if (_executionInstance != null) {
                _executionInstance.cancel(false);
            }

            _executionInstance = _dirlistTimeoutExecutor.schedule(target, _timeout, TimeUnit.MILLISECONDS);
        }

        public synchronized void cancelTimeout() {
            _executionInstance.cancel(false);
        }
    }

    /**
     * Check whether the given path matches against a list of allowed
     * write paths.
     *
     * @param path the path which is going to be checked
     */
    private boolean isWriteAllowed(FsPath path) {
        for (FsPath prefix : _writePaths) {
            if (path.startsWith(prefix)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check whether the given path matches against a list of allowed
     * read paths.
     *
     * @param path the path which is going to be checked
     */
    private boolean isReadAllowed(FsPath path) {
        for (FsPath prefix : _readPaths) {
            if (path.startsWith(prefix)) {
                return true;
            }
        }
        return false;
    }

    private Inet4Address getFirstIpv4(Collection<NetIFContainer> interfaces) {
        for (NetIFContainer container : interfaces) {
            for (Object ip : container.getInetAddresses()) {
                if (ip instanceof Inet4Address) {
                    return (Inet4Address) ip;
                }
            }
        }
        return null;
    }

    /**
     * Requests to start movers are processed synchronously by the
     * Transfer class. This message handler will only ever receive
     * replies for those requests for which the Transfer class timed
     * out or interrupted.
     *
     * To avoid that orphaned movers fill a transfer slot on the pool,
     * we kill it right away.
     */
    public void messageArrived(PoolIoFileMessage message) {
        String pool = message.getPoolName();
        int moverId = message.getMoverId();
        try {
            PoolMoverKillMessage killMessage = new PoolMoverKillMessage(pool, moverId);
            killMessage.setReplyRequired(false);
            _poolStub.send(new CellPath(pool), killMessage);
        } catch (NoRouteToCellException e) {
            _log.error("Failed to kill mover {}/{}: {}", new Object[] { pool, moverId, e.getMessage() });
        }
    }

    public void messageArrived(XrootdDoorAdressInfoMessage msg) {
        _log.debug("Received redirect msg from mover");

        XrootdTransfer transfer = _transfers.get(msg.getXrootdFileHandle());

        if (transfer != null) {
            transfer.setUUIDSupported(msg.isUUIDEnabledPool());
            // REVISIT: pick the first IPv4 address from the
            // collection at this point, we can't determine, which of
            // the pool IP-addresses is the right one, so we select
            // the first
            Collection<NetIFContainer> interfaces = Collections.checkedCollection(msg.getNetworkInterfaces(),
                    NetIFContainer.class);
            Inet4Address ip = getFirstIpv4(interfaces);

            if (ip != null) {
                InetSocketAddress address = new InetSocketAddress(ip, msg.getServerPort());
                transfer.redirect(address);
            } else {
                _log.warn("No valid IP-address received from pool. Redirection not possible");
                transfer.redirect(null);
            }
        }
    }

    public void messageArrived(DoorTransferFinishedMessage msg) {
        if ((msg.getProtocolInfo() instanceof XrootdProtocolInfo)) {
            XrootdProtocolInfo info = (XrootdProtocolInfo) msg.getProtocolInfo();
            XrootdTransfer transfer = _transfers.get(info.getXrootdFileHandle());
            if (transfer != null) {
                transfer.finished(msg);
            }
        } else {
            _log.warn("Ignoring unknown protocol info {} from pool {}", msg.getProtocolInfo(), msg.getPoolName());
        }
    }

    /**
     * Try to find callback registered in listPath(...) and process the
     * response there
     * @param msg The reply to a PnfsListDirectoryMessage sent earlier.
     */
    public void messageArrived(PnfsListDirectoryMessage msg) {
        UUID uuid = msg.getUUID();
        DirlistRequestHandler request = _requestHandlers.get(uuid);

        if (request == null) {
            _log.info("Did not find the callback for directory listing " + "message with UUID {}.", uuid);
            return;
        }

        if (msg.getReturnCode() == 0 && msg.isFinal()) {
            request.success(msg);
        } else if (msg.getReturnCode() == 0) {
            request.continueListing(msg);
        } else {
            request.failure(msg);
        }
    }

    public FileMetaData getFileMetaData(FsPath fullPath, Subject subject) throws CacheException {
        PnfsHandler pnfsHandler = new PnfsHandler(_pnfs, subject);
        return new FileMetaData(
                pnfsHandler.getFileAttributes(fullPath.toString(), FileMetaData.getKnownFileAttributes()));
    }

    public FileMetaData[] getMultipleFileMetaData(FsPath[] allPaths, Subject subject) throws CacheException {
        FileMetaData[] allMetas = new FileMetaData[allPaths.length];

        // TODO: Use SpreadAndWait
        for (int i = 0; i < allPaths.length; i++) {
            try {
                allMetas[i] = getFileMetaData(allPaths[i], subject);
            } catch (CacheException e) {
                if (e.getRc() != CacheException.FILE_NOT_FOUND && e.getRc() != CacheException.NOT_IN_TRASH) {
                    throw e;
                }
            }
        }
        return allMetas;
    }

    /**
     * To allow the transfer monitoring in the httpd cell to recognize us
     * as a door, we have to emulate LoginManager.  To emulate
     * LoginManager we list ourselves as our child.
     */
    public final static String hh_get_children = "[-binary]";

    public Object ac_get_children(Args args) {
        boolean binary = args.getOpt("binary") != null;
        if (binary) {
            String[] list = new String[] { _cellName };
            return new LoginManagerChildrenInfo(_cellName, _domainName, list);
        } else {
            return _cellName;
        }
    }

    public final static String hh_get_door_info = "[-binary]";
    public final static String fh_get_door_info = "Provides information about the door and current transfers";

    public Object ac_get_door_info(Args args) {
        List<IoDoorEntry> entries = new ArrayList<IoDoorEntry>();
        for (Transfer transfer : _transfers.values()) {
            entries.add(transfer.getIoDoorEntry());
        }

        IoDoorInfo doorInfo = new IoDoorInfo(_cellName, _domainName);
        doorInfo.setProtocol(XROOTD_PROTOCOL_STRING, XROOTD_PROTOCOL_VERSION);
        doorInfo.setOwner("");
        doorInfo.setProcess("");
        doorInfo.setIoDoorEntries(entries.toArray(new IoDoorEntry[entries.size()]));
        return (args.getOpt("binary") != null) ? doorInfo : doorInfo.toString();
    }

    public static final String hh_kill_mover = " <pool> <moverid> # kill transfer on the pool";

    public String ac_kill_mover_$_2(Args args) throws NumberFormatException {
        int mover = Integer.parseInt(args.argv(1));
        String pool = args.argv(0);

        for (Transfer transfer : _transfers.values()) {
            if (transfer.getMoverId() == mover && transfer.getPool() != null && transfer.getPool().equals(pool)) {

                transfer.killMover(0);
                return "Kill request to the mover " + mover + " has been submitted";
            }
        }
        return "mover " + mover + " not found on the pool " + pool;
    }
}