193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair/*
293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair * Copyright 2007 the original author or authors.
393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair *
493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair * Licensed under the Apache License, Version 2.0 (the "License");
593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair * you may not use this file except in compliance with the License.
693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair * You may obtain a copy of the License at
793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair *
893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair *      http://www.apache.org/licenses/LICENSE-2.0
993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair *
1093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair * Unless required by applicable law or agreed to in writing, software
1193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair * distributed under the License is distributed on an "AS IS" BASIS,
1293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair * See the License for the specific language governing permissions and
1493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair * limitations under the License.
1593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair */
1693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismairpackage org.mockftpserver.core.session;
1793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
18dfa40a06dff44f29d8d5e1d3186055ad325fc7b9chrismairimport org.slf4j.Logger;
19dfa40a06dff44f29d8d5e1d3186055ad325fc7b9chrismairimport org.slf4j.LoggerFactory;
20870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismairimport org.mockftpserver.core.MockFtpServerException;
21870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismairimport org.mockftpserver.core.command.Command;
22870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismairimport org.mockftpserver.core.command.CommandHandler;
23870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismairimport org.mockftpserver.core.command.CommandNames;
24870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismairimport org.mockftpserver.core.socket.DefaultServerSocketFactory;
25870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismairimport org.mockftpserver.core.socket.DefaultSocketFactory;
26870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismairimport org.mockftpserver.core.socket.ServerSocketFactory;
27870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismairimport org.mockftpserver.core.socket.SocketFactory;
28870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismairimport org.mockftpserver.core.util.Assert;
29870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismairimport org.mockftpserver.core.util.AssertFailedException;
30870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair
3149deb463d1cc3132e4aa60bfd4469398c57c1745chrismairimport java.io.BufferedReader;
3249deb463d1cc3132e4aa60bfd4469398c57c1745chrismairimport java.io.ByteArrayOutputStream;
3349deb463d1cc3132e4aa60bfd4469398c57c1745chrismairimport java.io.IOException;
3449deb463d1cc3132e4aa60bfd4469398c57c1745chrismairimport java.io.InputStream;
3549deb463d1cc3132e4aa60bfd4469398c57c1745chrismairimport java.io.InputStreamReader;
3649deb463d1cc3132e4aa60bfd4469398c57c1745chrismairimport java.io.OutputStream;
3749deb463d1cc3132e4aa60bfd4469398c57c1745chrismairimport java.io.PrintWriter;
3849deb463d1cc3132e4aa60bfd4469398c57c1745chrismairimport java.io.Writer;
3993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismairimport java.net.InetAddress;
4093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismairimport java.net.ServerSocket;
4193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismairimport java.net.Socket;
4293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismairimport java.net.SocketTimeoutException;
4349deb463d1cc3132e4aa60bfd4469398c57c1745chrismairimport java.util.ArrayList;
4449deb463d1cc3132e4aa60bfd4469398c57c1745chrismairimport java.util.HashMap;
4549deb463d1cc3132e4aa60bfd4469398c57c1745chrismairimport java.util.List;
4649deb463d1cc3132e4aa60bfd4469398c57c1745chrismairimport java.util.Map;
4749deb463d1cc3132e4aa60bfd4469398c57c1745chrismairimport java.util.Set;
4849deb463d1cc3132e4aa60bfd4469398c57c1745chrismairimport java.util.StringTokenizer;
4993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
5093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair/**
5193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair * Default implementation of the {@link Session} interface.
52870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair *
5393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair * @author Chris Mair
54870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair * @version $Revision$ - $Date$
5593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair */
5693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismairpublic class DefaultSession implements Session {
5793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
58dfa40a06dff44f29d8d5e1d3186055ad325fc7b9chrismair    private static final Logger LOG = LoggerFactory.getLogger(DefaultSession.class);
59870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair    private static final String END_OF_LINE = "\r\n";
60725fc0ad3d852d13fbbfd31b07ac20fc2a16eec2chrismair    protected static final int DEFAULT_CLIENT_DATA_PORT = 21;
61870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair
62725fc0ad3d852d13fbbfd31b07ac20fc2a16eec2chrismair    protected SocketFactory socketFactory = new DefaultSocketFactory();
63725fc0ad3d852d13fbbfd31b07ac20fc2a16eec2chrismair    protected ServerSocketFactory serverSocketFactory = new DefaultServerSocketFactory();
64870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair
6593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    private BufferedReader controlConnectionReader;
6693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    private Writer controlConnectionWriter;
6793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    private Socket controlSocket;
6893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    private Socket dataSocket;
6993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    ServerSocket passiveModeDataSocket; // non-private for testing
7093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    private InputStream dataInputStream;
7193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    private OutputStream dataOutputStream;
7293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    private Map commandHandlers;
7393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    private int clientDataPort = DEFAULT_CLIENT_DATA_PORT;
7493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    private InetAddress clientHost;
7593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    private InetAddress serverHost;
7693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    private Map attributes = new HashMap();
7793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    private volatile boolean terminate = false;
7893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
7993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
8093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * Create a new initialized instance
81870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     *
82870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     * @param controlSocket   - the control connection socket
8393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @param commandHandlers - the Map of command name -> CommandHandler. It is assumed that the
84870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     *                        command names are all normalized to upper case. See {@link Command#normalizeName(String)}.
8593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
8693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    public DefaultSession(Socket controlSocket, Map commandHandlers) {
8793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        Assert.notNull(controlSocket, "controlSocket");
8893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        Assert.notNull(commandHandlers, "commandHandlers");
8993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
9093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        this.controlSocket = controlSocket;
9193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        this.commandHandlers = commandHandlers;
9293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        this.serverHost = controlSocket.getLocalAddress();
9393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
9493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
9593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
9693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * Return the InetAddress representing the client host for this session
97870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     *
9893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @return the client host
9993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @see org.mockftpserver.core.session.Session#getClientHost()
10093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
10193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    public InetAddress getClientHost() {
10293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        return controlSocket.getInetAddress();
10393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
10493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
10593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
10693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * Return the InetAddress representing the server host for this session
107870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     *
10893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @return the server host
10993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @see org.mockftpserver.core.session.Session#getServerHost()
11093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
11193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    public InetAddress getServerHost() {
11293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        return serverHost;
11393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
11493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
11593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
11693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * Send the specified reply code and text across the control connection.
11793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * The reply text is trimmed before being sent.
118870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     *
11993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @param code - the reply code
12093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @param text - the reply text to send; may be null
12193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
12293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    public void sendReply(int code, String text) {
12393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        assertValidReplyCode(code);
12493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
12593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        StringBuffer buffer = new StringBuffer(Integer.toString(code));
126870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair
12793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        if (text != null && text.length() > 0) {
12893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            String replyText = text.trim();
12993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            if (replyText.indexOf("\n") != -1) {
13093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                int lastIndex = replyText.lastIndexOf("\n");
13193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                buffer.append("-");
13293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                for (int i = 0; i < replyText.length(); i++) {
13393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                    char c = replyText.charAt(i);
13493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                    buffer.append(c);
13593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                    if (i == lastIndex) {
13636a506bc75689778c2cf5a3898d9227f49f9a6c9chrismair                        buffer.append(Integer.toString(code));
13736a506bc75689778c2cf5a3898d9227f49f9a6c9chrismair                        buffer.append(" ");
13893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                    }
13993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                }
140870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair            } else {
14193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                buffer.append(" ");
14293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                buffer.append(replyText);
14393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            }
14493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
14593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        LOG.debug("Sending Reply [" + buffer.toString() + "]");
14693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        writeLineToControlConnection(buffer.toString());
14793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
14893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
14993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
15093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @see org.mockftpserver.core.session.Session#openDataConnection()
15193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
15293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    public void openDataConnection() {
15393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        try {
15493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            if (passiveModeDataSocket != null) {
15593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                LOG.debug("Waiting for (passive mode) client connection from client host [" + clientHost
15693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                        + "] on port " + passiveModeDataSocket.getLocalPort());
15793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                // TODO set socket timeout
15893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                try {
15993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                    dataSocket = passiveModeDataSocket.accept();
16093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                    LOG.debug("Successful (passive mode) client connection to port "
16193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                            + passiveModeDataSocket.getLocalPort());
16293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                }
16393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                catch (SocketTimeoutException e) {
16493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                    throw new MockFtpServerException(e);
16593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                }
166870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair            } else {
16793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                Assert.notNull(clientHost, "clientHost");
16893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                LOG.debug("Connecting to client host [" + clientHost + "] on data port [" + clientDataPort
16993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                        + "]");
17093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                dataSocket = socketFactory.createSocket(clientHost, clientDataPort);
17193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            }
17293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            dataOutputStream = dataSocket.getOutputStream();
17393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            dataInputStream = dataSocket.getInputStream();
17493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
17593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        catch (IOException e) {
17693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            throw new MockFtpServerException(e);
17793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
17893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
17993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
18093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
18193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * Switch to passive mode
182870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     *
18393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @return the local port to be connected to by clients for data transfers
18493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @see org.mockftpserver.core.session.Session#switchToPassiveMode()
18593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
18693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    public int switchToPassiveMode() {
18793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        try {
18893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            passiveModeDataSocket = serverSocketFactory.createServerSocket(0);
18993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            return passiveModeDataSocket.getLocalPort();
19093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
19193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        catch (IOException e) {
19293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            throw new MockFtpServerException("Error opening passive mode server data socket", e);
19393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
19493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
19593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
19693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
19793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @see org.mockftpserver.core.session.Session#closeDataConnection()
19893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
19993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    public void closeDataConnection() {
20093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        try {
20193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            LOG.debug("Flushing and closing client data socket");
20293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            dataOutputStream.flush();
20393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            dataOutputStream.close();
20493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            dataInputStream.close();
20593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            dataSocket.close();
20693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
20793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        catch (IOException e) {
20893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            LOG.error("Error closing client data socket", e);
20993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
21093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
21193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
21293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
21393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * Write a single line to the control connection, appending a newline
214870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     *
21593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @param line - the line to write
21693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
21793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    private void writeLineToControlConnection(String line) {
21893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        try {
219870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair            controlConnectionWriter.write(line + END_OF_LINE);
22093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            controlConnectionWriter.flush();
22193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
22293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        catch (IOException e) {
22393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            LOG.error("Error writing to control connection", e);
22493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            throw new MockFtpServerException("Error writing to control connection", e);
22593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
22693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
22793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
22893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
22993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @see org.mockftpserver.core.session.Session#close()
23093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
23193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    public void close() {
23293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        LOG.trace("close()");
23393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        terminate = true;
23493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
23593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
23693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
23793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @see org.mockftpserver.core.session.Session#sendData(byte[], int)
23893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
23993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    public void sendData(byte[] data, int numBytes) {
24093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        Assert.notNull(data, "data");
24193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        try {
24293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            dataOutputStream.write(data, 0, numBytes);
24393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
24493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        catch (IOException e) {
24593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            throw new MockFtpServerException(e);
24693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
24793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
24893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
24993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
25093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @see org.mockftpserver.core.session.Session#readData()
25193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
25293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    public byte[] readData() {
25363789083e958081738bc2208fa5abdc5a4ef2084chrismair        return readData(Integer.MAX_VALUE);
25463789083e958081738bc2208fa5abdc5a4ef2084chrismair    }
25593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
25663789083e958081738bc2208fa5abdc5a4ef2084chrismair    /**
25763789083e958081738bc2208fa5abdc5a4ef2084chrismair     * @see org.mockftpserver.core.session.Session#readData()
25863789083e958081738bc2208fa5abdc5a4ef2084chrismair     */
25963789083e958081738bc2208fa5abdc5a4ef2084chrismair    public byte[] readData(int numBytes) {
26093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
26163789083e958081738bc2208fa5abdc5a4ef2084chrismair        int numBytesRead = 0;
26293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        try {
26363789083e958081738bc2208fa5abdc5a4ef2084chrismair            while (numBytesRead < numBytes) {
26493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                int b = dataInputStream.read();
26593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                if (b == -1) {
26693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                    break;
26793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                }
26893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                bytes.write(b);
26963789083e958081738bc2208fa5abdc5a4ef2084chrismair                numBytesRead++;
27093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            }
27193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            return bytes.toByteArray();
27293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
27393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        catch (IOException e) {
27493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            throw new MockFtpServerException(e);
27593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
27693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
27793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
27893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
27993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * Wait for and read the command sent from the client on the control connection.
280870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     *
28193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @return the Command sent from the client; may be null if the session has been closed
282870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     *         <p/>
283870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     *         Package-private to enable testing
28493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
28593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    Command readCommand() {
28693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
28712674b20efcdd79e793d4ca3c7697232658aa036chrismair        final long socketReadIntervalMilliseconds = 20L;
28893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
28993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        try {
29093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            while (true) {
29193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                if (terminate) {
29293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                    return null;
29393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                }
29493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                // Don't block; only read command when it is available
29593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                if (controlConnectionReader.ready()) {
29693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                    String command = controlConnectionReader.readLine();
29793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                    LOG.info("Received command: [" + command + "]");
29893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                    return parseCommand(command);
29993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                }
30093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                try {
30193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                    Thread.sleep(socketReadIntervalMilliseconds);
30293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                }
30393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                catch (InterruptedException e) {
30493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                    throw new MockFtpServerException(e);
30593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                }
30693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            }
30793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
30893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        catch (IOException e) {
30993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            LOG.error("Read failed", e);
31093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            throw new MockFtpServerException(e);
31193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
31293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
31393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
31493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
31593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * Parse the command String into a Command object
316870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     *
31793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @param commandString - the command String
31893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @return the Command object parsed from the command String
31993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
32093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    Command parseCommand(String commandString) {
32193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        Assert.notNullOrEmpty(commandString, "commandString");
32293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
32393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        List parameters = new ArrayList();
32493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        String name;
32593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
32693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        int indexOfFirstSpace = commandString.indexOf(" ");
32793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        if (indexOfFirstSpace != -1) {
32893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            name = commandString.substring(0, indexOfFirstSpace);
32993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            StringTokenizer tokenizer = new StringTokenizer(commandString.substring(indexOfFirstSpace + 1),
33093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                    ",");
33193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            while (tokenizer.hasMoreTokens()) {
33293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                parameters.add(tokenizer.nextToken());
33393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            }
334870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair        } else {
33593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            name = commandString;
33693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
33793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
33893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        String[] parametersArray = new String[parameters.size()];
33993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        return new Command(name, (String[]) parameters.toArray(parametersArray));
34093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
34193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
34293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
34393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @see org.mockftpserver.core.session.Session#setClientDataHost(java.net.InetAddress)
34493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
34593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    public void setClientDataHost(InetAddress clientHost) {
34693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        this.clientHost = clientHost;
34793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
34893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
34993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
35093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @see org.mockftpserver.core.session.Session#setClientDataPort(int)
35193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
35293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    public void setClientDataPort(int dataPort) {
35393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        this.clientDataPort = dataPort;
35493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
35593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        // Clear out any passive data connection mode information
35693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        if (passiveModeDataSocket != null) {
35793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            try {
35893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                this.passiveModeDataSocket.close();
35993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            }
36093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            catch (IOException e) {
36193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                throw new MockFtpServerException(e);
36293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            }
36393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            passiveModeDataSocket = null;
36493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
36593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
36693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
36793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
36893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @see java.lang.Runnable#run()
36993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
37093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    public void run() {
37193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        try {
37293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
37393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            InputStream inputStream = controlSocket.getInputStream();
37493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            OutputStream outputStream = controlSocket.getOutputStream();
37593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            controlConnectionReader = new BufferedReader(new InputStreamReader(inputStream));
37693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            controlConnectionWriter = new PrintWriter(outputStream, true);
37793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
37893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            LOG.debug("Starting the session...");
379870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair
38093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            CommandHandler connectCommandHandler = (CommandHandler) commandHandlers.get(CommandNames.CONNECT);
38193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            connectCommandHandler.handleCommand(new Command(CommandNames.CONNECT, new String[0]), this);
38293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
38393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            while (!terminate) {
38493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                readAndProcessCommand();
38593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            }
38693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
38793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        catch (Exception e) {
388dfa40a06dff44f29d8d5e1d3186055ad325fc7b9chrismair            LOG.error("Error:", e);
38993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            throw new MockFtpServerException(e);
39093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
39193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        finally {
39293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            LOG.debug("Cleaning up the session");
39393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            try {
39493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                controlConnectionReader.close();
39593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair                controlConnectionWriter.close();
39693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            }
39793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            catch (IOException e) {
398dfa40a06dff44f29d8d5e1d3186055ad325fc7b9chrismair                LOG.error("Error:", e);
39993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            }
40093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            LOG.debug("Session stopped.");
40193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
40293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
40393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
40493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
40593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * Read and process the next command from the control connection
406870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     *
40736a506bc75689778c2cf5a3898d9227f49f9a6c9chrismair     * @throws Exception - if any error occurs
40893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
40993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    private void readAndProcessCommand() throws Exception {
41093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
41193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        Command command = readCommand();
41293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        if (command != null) {
41393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            String normalizedCommandName = Command.normalizeName(command.getName());
41493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            CommandHandler commandHandler = (CommandHandler) commandHandlers.get(normalizedCommandName);
41549deb463d1cc3132e4aa60bfd4469398c57c1745chrismair
41649deb463d1cc3132e4aa60bfd4469398c57c1745chrismair            if (commandHandler == null) {
41749deb463d1cc3132e4aa60bfd4469398c57c1745chrismair                commandHandler = (CommandHandler) commandHandlers.get(CommandNames.UNSUPPORTED);
41849deb463d1cc3132e4aa60bfd4469398c57c1745chrismair            }
41949deb463d1cc3132e4aa60bfd4469398c57c1745chrismair
42093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            Assert.notNull(commandHandler, "CommandHandler for command [" + normalizedCommandName + "]");
42193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair            commandHandler.handleCommand(command, this);
42293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        }
42393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
42493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
42593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
42693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * Assert that the specified number is a valid reply code
427870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     *
42893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @param replyCode - the reply code to check
42993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
43093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    private void assertValidReplyCode(int replyCode) {
43193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        Assert.isTrue(replyCode > 0, "The number [" + replyCode + "] is not a valid reply code");
43293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
43393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
43493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
43593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * Return the attribute value for the specified name. Return null if no attribute value
43693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * exists for that name or if the attribute value is null.
437870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     *
43893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @param name - the attribute name; may not be null
43993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @return the value of the attribute stored under name; may be null
44093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @see org.mockftpserver.core.session.Session#getAttribute(java.lang.String)
44193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
44293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    public Object getAttribute(String name) {
44393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        Assert.notNull(name, "name");
44493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        return attributes.get(name);
44593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
44693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
44793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
44893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * Store the value under the specified attribute name.
449870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     *
450870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     * @param name  - the attribute name; may not be null
45193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @param value - the attribute value; may be null
45293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @see org.mockftpserver.core.session.Session#setAttribute(java.lang.String, java.lang.Object)
45393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
45493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    public void setAttribute(String name, Object value) {
45593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        Assert.notNull(name, "name");
45693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        attributes.put(name, value);
45793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
45893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
45993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
46093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * Return the Set of names under which attributes have been stored on this session.
46193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * Returns an empty Set if no attribute values are stored.
462870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     *
46393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @return the Set of attribute names
46493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @see org.mockftpserver.core.session.Session#getAttributeNames()
46593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
46693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    public Set getAttributeNames() {
46793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        return attributes.keySet();
46893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
46993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
47093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    /**
47193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * Remove the attribute value for the specified name. Do nothing if no attribute
47293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * value is stored for the specified name.
473870d738ae5b29b07bdf28c01e1ce55ad30dcecdcchrismair     *
47493102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @param name - the attribute name; may not be null
47593102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @throws AssertFailedException - if name is null
47693102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     * @see org.mockftpserver.core.session.Session#removeAttribute(java.lang.String)
47793102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair     */
47893102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    public void removeAttribute(String name) {
47993102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        Assert.notNull(name, "name");
48093102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair        attributes.remove(name);
48193102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair    }
48293102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair
48393102446a7b7c3d17888064b4e2e4e5cb534e6d0chrismair}
484