19d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair/*
29d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * Copyright 2007 the original author or authors.
39d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair *
49d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * Licensed under the Apache License, Version 2.0 (the "License");
59d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * you may not use this file except in compliance with the License.
69d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * You may obtain a copy of the License at
79d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair *
89d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair *      http://www.apache.org/licenses/LICENSE-2.0
99d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair *
109d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * Unless required by applicable law or agreed to in writing, software
119d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * distributed under the License is distributed on an "AS IS" BASIS,
129d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * See the License for the specific language governing permissions and
149d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * limitations under the License.
159d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair */
169d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairpackage org.mockftpserver.core.session;
179d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
189d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.io.BufferedReader;
199d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.io.ByteArrayOutputStream;
209d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.io.IOException;
219d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.io.InputStream;
229d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.io.InputStreamReader;
239d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.io.OutputStream;
249d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.io.PrintWriter;
259d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.io.Writer;
269d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.net.InetAddress;
279d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.net.ServerSocket;
289d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.net.Socket;
299d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.net.SocketTimeoutException;
309d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.util.ArrayList;
319d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.util.HashMap;
329d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.util.List;
339d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.util.Map;
349d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.util.Set;
359d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport java.util.StringTokenizer;
369d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
379d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.apache.log4j.Logger;
389d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.MockFtpServerException;
399d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.command.Command;
409d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.command.CommandHandler;
419d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.command.CommandNames;
429d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.socket.DefaultServerSocketFactory;
439d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.socket.DefaultSocketFactory;
449d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.socket.ServerSocketFactory;
459d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.socket.SocketFactory;
469d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.util.Assert;
479d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairimport org.mockftpserver.core.util.AssertFailedException;
489d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
499d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair/**
509d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * Default implementation of the {@link Session} interface.
519d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair *
529d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * @version $Revision$ - $Date$
539d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair *
549d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair * @author Chris Mair
559d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair */
569d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismairpublic class DefaultSession implements Session {
579d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
589d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private static final Logger LOG = Logger.getLogger(DefaultSession.class);
599d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    static final int DEFAULT_CLIENT_DATA_PORT = 21;
609d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
619d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    SocketFactory socketFactory = new DefaultSocketFactory();
629d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    ServerSocketFactory serverSocketFactory = new DefaultServerSocketFactory();
639d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
649d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private BufferedReader controlConnectionReader;
659d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private Writer controlConnectionWriter;
669d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private Socket controlSocket;
679d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private Socket dataSocket;
689d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    ServerSocket passiveModeDataSocket; // non-private for testing
699d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private InputStream dataInputStream;
709d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private OutputStream dataOutputStream;
719d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private Map commandHandlers;
729d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private int clientDataPort = DEFAULT_CLIENT_DATA_PORT;
739d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private InetAddress clientHost;
749d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private InetAddress serverHost;
759d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private Map attributes = new HashMap();
769d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private volatile boolean terminate = false;
779d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
789d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
799d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Create a new initialized instance
809d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
819d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @param controlSocket - the control connection socket
829d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @param commandHandlers - the Map of command name -> CommandHandler. It is assumed that the
839d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *      command names are all normalized to upper case. See {@link Command#normalizeName(String)}.
849d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
859d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public DefaultSession(Socket controlSocket, Map commandHandlers) {
869d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        Assert.notNull(controlSocket, "controlSocket");
879d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        Assert.notNull(commandHandlers, "commandHandlers");
889d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
899d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        this.controlSocket = controlSocket;
909d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        this.commandHandlers = commandHandlers;
919d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        this.serverHost = controlSocket.getLocalAddress();
929d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
939d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
949d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
959d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Return the InetAddress representing the client host for this session
969d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
979d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @return the client host
989d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
999d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @see org.mockftpserver.core.session.Session#getClientHost()
1009d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
1019d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public InetAddress getClientHost() {
1029d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        return controlSocket.getInetAddress();
1039d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
1049d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
1059d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
1069d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Return the InetAddress representing the server host for this session
1079d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
1089d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @return the server host
1099d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
1109d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @see org.mockftpserver.core.session.Session#getServerHost()
1119d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
1129d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public InetAddress getServerHost() {
1139d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        return serverHost;
1149d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
1159d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
1169d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
1179d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Send the specified reply code and text across the control connection.
1189d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * The reply text is trimmed before being sent.
1199d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
1209d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @param code - the reply code
1219d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @param text - the reply text to send; may be null
1229d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
1239d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public void sendReply(int code, String text) {
1249d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        assertValidReplyCode(code);
1259d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
1269d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        StringBuffer buffer = new StringBuffer(Integer.toString(code));
1279d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
1289d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        if (text != null && text.length() > 0) {
1299d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            String replyText = text.trim();
1309d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            if (replyText.indexOf("\n") != -1) {
1319d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                int lastIndex = replyText.lastIndexOf("\n");
1329d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                buffer.append("-");
1339d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                for (int i = 0; i < replyText.length(); i++) {
1349d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    char c = replyText.charAt(i);
1359d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    buffer.append(c);
1369d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    if (i == lastIndex) {
1379d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                        buffer.append(Integer.toString(code) + " ");
1389d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    }
1399d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                }
1409d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
1419d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            else {
1429d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                buffer.append(" ");
1439d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                buffer.append(replyText);
1449d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
1459d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
1469d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        LOG.debug("Sending Reply [" + buffer.toString() + "]");
1479d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        writeLineToControlConnection(buffer.toString());
1489d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
1499d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
1509d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
1519d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @see org.mockftpserver.core.session.Session#openDataConnection()
1529d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
1539d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public void openDataConnection() {
1549d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        try {
1559d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            if (passiveModeDataSocket != null) {
1569d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                LOG.debug("Waiting for (passive mode) client connection from client host [" + clientHost
1579d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                        + "] on port " + passiveModeDataSocket.getLocalPort());
1589d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                // TODO set socket timeout
1599d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                try {
1609d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    dataSocket = passiveModeDataSocket.accept();
1619d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    LOG.debug("Successful (passive mode) client connection to port "
1629d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                            + passiveModeDataSocket.getLocalPort());
1639d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                }
1649d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                catch (SocketTimeoutException e) {
1659d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    throw new MockFtpServerException(e);
1669d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                }
1679d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
1689d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            else {
1699d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                Assert.notNull(clientHost, "clientHost");
1709d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                LOG.debug("Connecting to client host [" + clientHost + "] on data port [" + clientDataPort
1719d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                        + "]");
1729d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                dataSocket = socketFactory.createSocket(clientHost, clientDataPort);
1739d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
1749d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            dataOutputStream = dataSocket.getOutputStream();
1759d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            dataInputStream = dataSocket.getInputStream();
1769d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
1779d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        catch (IOException e) {
1789d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            throw new MockFtpServerException(e);
1799d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
1809d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
1819d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
1829d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
1839d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Switch to passive mode
1849d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
1859d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @return the local port to be connected to by clients for data transfers
1869d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
1879d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @see org.mockftpserver.core.session.Session#switchToPassiveMode()
1889d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
1899d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public int switchToPassiveMode() {
1909d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        try {
1919d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            passiveModeDataSocket = serverSocketFactory.createServerSocket(0);
1929d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            return passiveModeDataSocket.getLocalPort();
1939d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
1949d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        catch (IOException e) {
1959d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            throw new MockFtpServerException("Error opening passive mode server data socket", e);
1969d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
1979d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
1989d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
1999d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
2009d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @see org.mockftpserver.core.session.Session#closeDataConnection()
2019d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
2029d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public void closeDataConnection() {
2039d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        try {
2049d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            LOG.debug("Flushing and closing client data socket");
2059d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            dataOutputStream.flush();
2069d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            dataOutputStream.close();
2079d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            dataInputStream.close();
2089d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            dataSocket.close();
2099d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
2109d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        catch (IOException e) {
2119d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            LOG.error("Error closing client data socket", e);
2129d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
2139d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
2149d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2159d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
2169d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Write a single line to the control connection, appending a newline
2179d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
2189d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @param line - the line to write
2199d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
2209d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private void writeLineToControlConnection(String line) {
2219d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        try {
2229d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            controlConnectionWriter.write(line + "\n");
2239d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            controlConnectionWriter.flush();
2249d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
2259d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        catch (IOException e) {
2269d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            LOG.error("Error writing to control connection", e);
2279d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            throw new MockFtpServerException("Error writing to control connection", e);
2289d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
2299d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
2309d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2319d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
2329d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @see org.mockftpserver.core.session.Session#close()
2339d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
2349d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public void close() {
2359d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        LOG.trace("close()");
2369d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        terminate = true;
2379d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
2389d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2399d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
2409d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @see org.mockftpserver.core.session.Session#sendData(byte[], int)
2419d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
2429d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public void sendData(byte[] data, int numBytes) {
2439d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        Assert.notNull(data, "data");
2449d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        try {
2459d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            dataOutputStream.write(data, 0, numBytes);
2469d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
2479d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        catch (IOException e) {
2489d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            throw new MockFtpServerException(e);
2499d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
2509d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
2519d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2529d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
2539d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @see org.mockftpserver.core.session.Session#readData()
2549d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
2559d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public byte[] readData() {
2569d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2579d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
2589d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2599d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        try {
2609d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            while (true) {
2619d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                int b = dataInputStream.read();
2629d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                if (b == -1) {
2639d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    break;
2649d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                }
2659d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                bytes.write(b);
2669d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
2679d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            return bytes.toByteArray();
2689d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
2699d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        catch (IOException e) {
2709d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            throw new MockFtpServerException(e);
2719d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
2729d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
2739d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2749d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
2759d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Wait for and read the command sent from the client on the control connection.
2769d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
2779d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @return the Command sent from the client; may be null if the session has been closed
2789d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
2799d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Package-private to enable testing
2809d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
2819d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    Command readCommand() {
2829d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2839d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        final long socketReadIntervalMilliseconds = 100L;
2849d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
2859d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        try {
2869d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            while (true) {
2879d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                if (terminate) {
2889d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    return null;
2899d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                }
2909d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                // Don't block; only read command when it is available
2919d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                if (controlConnectionReader.ready()) {
2929d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    String command = controlConnectionReader.readLine();
2939d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    LOG.info("Received command: [" + command + "]");
2949d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    return parseCommand(command);
2959d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                }
2969d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                try {
2979d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    Thread.sleep(socketReadIntervalMilliseconds);
2989d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                }
2999d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                catch (InterruptedException e) {
3009d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    throw new MockFtpServerException(e);
3019d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                }
3029d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
3039d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
3049d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        catch (IOException e) {
3059d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            LOG.error("Read failed", e);
3069d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            throw new MockFtpServerException(e);
3079d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
3089d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
3099d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3109d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
3119d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Parse the command String into a Command object
3129d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
3139d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @param commandString - the command String
3149d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @return the Command object parsed from the command String
3159d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
3169d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    Command parseCommand(String commandString) {
3179d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        Assert.notNullOrEmpty(commandString, "commandString");
3189d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3199d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        List parameters = new ArrayList();
3209d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        String name;
3219d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3229d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        int indexOfFirstSpace = commandString.indexOf(" ");
3239d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        if (indexOfFirstSpace != -1) {
3249d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            name = commandString.substring(0, indexOfFirstSpace);
3259d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            StringTokenizer tokenizer = new StringTokenizer(commandString.substring(indexOfFirstSpace + 1),
3269d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                    ",");
3279d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            while (tokenizer.hasMoreTokens()) {
3289d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                parameters.add(tokenizer.nextToken());
3299d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
3309d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
3319d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        else {
3329d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            name = commandString;
3339d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
3349d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3359d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        String[] parametersArray = new String[parameters.size()];
3369d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        return new Command(name, (String[]) parameters.toArray(parametersArray));
3379d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
3389d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3399d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
3409d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @see org.mockftpserver.core.session.Session#setClientDataHost(java.net.InetAddress)
3419d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
3429d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public void setClientDataHost(InetAddress clientHost) {
3439d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        this.clientHost = clientHost;
3449d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
3459d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3469d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
3479d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @see org.mockftpserver.core.session.Session#setClientDataPort(int)
3489d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
3499d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public void setClientDataPort(int dataPort) {
3509d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        this.clientDataPort = dataPort;
3519d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3529d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        // Clear out any passive data connection mode information
3539d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        if (passiveModeDataSocket != null) {
3549d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            try {
3559d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                this.passiveModeDataSocket.close();
3569d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
3579d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            catch (IOException e) {
3589d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                throw new MockFtpServerException(e);
3599d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
3609d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            passiveModeDataSocket = null;
3619d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
3629d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
3639d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3649d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
3659d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @see java.lang.Runnable#run()
3669d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
3679d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public void run() {
3689d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        try {
3699d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3709d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            InputStream inputStream = controlSocket.getInputStream();
3719d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            OutputStream outputStream = controlSocket.getOutputStream();
3729d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            controlConnectionReader = new BufferedReader(new InputStreamReader(inputStream));
3739d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            controlConnectionWriter = new PrintWriter(outputStream, true);
3749d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3759d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            LOG.debug("Starting the session...");
3769d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3779d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            CommandHandler connectCommandHandler = (CommandHandler) commandHandlers.get(CommandNames.CONNECT);
3789d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            connectCommandHandler.handleCommand(new Command(CommandNames.CONNECT, new String[0]), this);
3799d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
3809d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            while (!terminate) {
3819d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                readAndProcessCommand();
3829d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
3839d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
3849d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        catch (Exception e) {
3859d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            LOG.error(e);
3869d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            throw new MockFtpServerException(e);
3879d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
3889d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        finally {
3899d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            LOG.debug("Cleaning up the session");
3909d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            try {
3919d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                controlConnectionReader.close();
3929d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                controlConnectionWriter.close();
3939d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
3949d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            catch (IOException e) {
3959d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                LOG.error(e);
3969d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair                throw new MockFtpServerException(e);
3979d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            }
3989d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            LOG.debug("Session stopped.");
3999d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
4009d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
4019d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
4029d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
4039d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Read and process the next command from the control connection
4049d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
4059d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @throws Exception
4069d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
4079d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private void readAndProcessCommand() throws Exception {
4089d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
4099d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        Command command = readCommand();
4109d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        if (command != null) {
4119d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            String normalizedCommandName = Command.normalizeName(command.getName());
4129d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            CommandHandler commandHandler = (CommandHandler) commandHandlers.get(normalizedCommandName);
4139d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            Assert.notNull(commandHandler, "CommandHandler for command [" + normalizedCommandName + "]");
4149d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair            commandHandler.handleCommand(command, this);
4159d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        }
4169d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
4179d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
4189d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
4199d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Assert that the specified number is a valid reply code
4209d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
4219d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @param replyCode - the reply code to check
4229d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
4239d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    private void assertValidReplyCode(int replyCode) {
4249d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        Assert.isTrue(replyCode > 0, "The number [" + replyCode + "] is not a valid reply code");
4259d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
4269d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
4279d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
4289d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Return the attribute value for the specified name. Return null if no attribute value
4299d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * exists for that name or if the attribute value is null.
4309d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @param name - the attribute name; may not be null
4319d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @return the value of the attribute stored under name; may be null
4329d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
4339d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @see org.mockftpserver.core.session.Session#getAttribute(java.lang.String)
4349d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
4359d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public Object getAttribute(String name) {
4369d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        Assert.notNull(name, "name");
4379d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        return attributes.get(name);
4389d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
4399d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
4409d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
4419d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Store the value under the specified attribute name.
4429d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @param name - the attribute name; may not be null
4439d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @param value - the attribute value; may be null
4449d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
4459d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @see org.mockftpserver.core.session.Session#setAttribute(java.lang.String, java.lang.Object)
4469d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
4479d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public void setAttribute(String name, Object value) {
4489d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        Assert.notNull(name, "name");
4499d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        attributes.put(name, value);
4509d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
4519d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
4529d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
4539d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Return the Set of names under which attributes have been stored on this session.
4549d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Returns an empty Set if no attribute values are stored.
4559d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @return the Set of attribute names
4569d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
4579d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @see org.mockftpserver.core.session.Session#getAttributeNames()
4589d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
4599d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public Set getAttributeNames() {
4609d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        return attributes.keySet();
4619d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
4629d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
4639d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    /**
4649d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * Remove the attribute value for the specified name. Do nothing if no attribute
4659d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * value is stored for the specified name.
4669d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @param name - the attribute name; may not be null
4679d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @throws AssertFailedException - if name is null
4689d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     *
4699d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     * @see org.mockftpserver.core.session.Session#removeAttribute(java.lang.String)
4709d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair     */
4719d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    public void removeAttribute(String name) {
4729d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        Assert.notNull(name, "name");
4739d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair        attributes.remove(name);
4749d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair    }
4759d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair
4769d9aece7b2c2865253fdd2946a4d11a4f642c5aechrismair}
477