177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair/*
277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * Copyright 2007 the original author or authors.
377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair *
477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * Licensed under the Apache License, Version 2.0 (the "License");
577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * you may not use this file except in compliance with the License.
677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * You may obtain a copy of the License at
777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair *
877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair *      http://www.apache.org/licenses/LICENSE-2.0
977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair *
1077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * Unless required by applicable law or agreed to in writing, software
1177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * distributed under the License is distributed on an "AS IS" BASIS,
1277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * See the License for the specific language governing permissions and
1477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * limitations under the License.
1577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair */
1677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairpackage org.mockftpserver.core.session;
1777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
1877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport org.apache.log4j.Logger;
1977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport org.mockftpserver.core.MockFtpServerException;
2077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport org.mockftpserver.core.command.Command;
2177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport org.mockftpserver.core.command.CommandHandler;
2277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport org.mockftpserver.core.command.CommandNames;
2377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport org.mockftpserver.core.socket.DefaultServerSocketFactory;
2477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport org.mockftpserver.core.socket.DefaultSocketFactory;
2577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport org.mockftpserver.core.socket.ServerSocketFactory;
2677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport org.mockftpserver.core.socket.SocketFactory;
2777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport org.mockftpserver.core.util.Assert;
2877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport org.mockftpserver.core.util.AssertFailedException;
2977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
3077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.io.BufferedReader;
3177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.io.ByteArrayOutputStream;
3277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.io.IOException;
3377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.io.InputStream;
3477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.io.InputStreamReader;
3577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.io.OutputStream;
3677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.io.PrintWriter;
3777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.io.Writer;
3877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.net.InetAddress;
3977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.net.ServerSocket;
4077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.net.Socket;
4177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.net.SocketTimeoutException;
4277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.util.ArrayList;
4377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.util.HashMap;
4477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.util.List;
4577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.util.Map;
4677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.util.Set;
4777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.util.StringTokenizer;
4877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
4977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair/**
5077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * Default implementation of the {@link Session} interface.
5177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair *
5277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * @author Chris Mair
5377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * @version $Revision$ - $Date$
5477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair */
5577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairpublic class DefaultSession implements Session {
5677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
5777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private static final Logger LOG = Logger.getLogger(DefaultSession.class);
5877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private static final String END_OF_LINE = "\r\n";
5977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    protected static final int DEFAULT_CLIENT_DATA_PORT = 21;
6077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
6177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    protected SocketFactory socketFactory = new DefaultSocketFactory();
6277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    protected ServerSocketFactory serverSocketFactory = new DefaultServerSocketFactory();
6377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
6477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private BufferedReader controlConnectionReader;
6577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private Writer controlConnectionWriter;
6677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private Socket controlSocket;
6777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private Socket dataSocket;
6877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    ServerSocket passiveModeDataSocket; // non-private for testing
6977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private InputStream dataInputStream;
7077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private OutputStream dataOutputStream;
7177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private Map commandHandlers;
7277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private int clientDataPort = DEFAULT_CLIENT_DATA_PORT;
7377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private InetAddress clientHost;
7477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private InetAddress serverHost;
7577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private Map attributes = new HashMap();
7677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private volatile boolean terminate = false;
7777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
7877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
7977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Create a new initialized instance
8077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
8177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param controlSocket   - the control connection socket
8277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param commandHandlers - the Map of command name -> CommandHandler. It is assumed that the
8377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *                        command names are all normalized to upper case. See {@link Command#normalizeName(String)}.
8477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
8577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public DefaultSession(Socket controlSocket, Map commandHandlers) {
8677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.notNull(controlSocket, "controlSocket");
8777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.notNull(commandHandlers, "commandHandlers");
8877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
8977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        this.controlSocket = controlSocket;
9077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        this.commandHandlers = commandHandlers;
9177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        this.serverHost = controlSocket.getLocalAddress();
9277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
9377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
9477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
9577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return the InetAddress representing the client host for this session
9677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
9777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the client host
9877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.core.session.Session#getClientHost()
9977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
10077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public InetAddress getClientHost() {
10177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return controlSocket.getInetAddress();
10277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
10377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
10477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
10577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return the InetAddress representing the server host for this session
10677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
10777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the server host
10877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.core.session.Session#getServerHost()
10977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
11077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public InetAddress getServerHost() {
11177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return serverHost;
11277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
11377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
11477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
11577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Send the specified reply code and text across the control connection.
11677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * The reply text is trimmed before being sent.
11777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
11877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param code - the reply code
11977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param text - the reply text to send; may be null
12077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
12177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public void sendReply(int code, String text) {
12277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        assertValidReplyCode(code);
12377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
12477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        StringBuffer buffer = new StringBuffer(Integer.toString(code));
12577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
12677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (text != null && text.length() > 0) {
12777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            String replyText = text.trim();
12877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            if (replyText.indexOf("\n") != -1) {
12977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                int lastIndex = replyText.lastIndexOf("\n");
13077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                buffer.append("-");
13177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                for (int i = 0; i < replyText.length(); i++) {
13277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    char c = replyText.charAt(i);
13377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    buffer.append(c);
13477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    if (i == lastIndex) {
13577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                        buffer.append(Integer.toString(code));
13677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                        buffer.append(" ");
13777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    }
13877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                }
13977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            } else {
14077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                buffer.append(" ");
14177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                buffer.append(replyText);
14277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            }
14377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
14477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        LOG.debug("Sending Reply [" + buffer.toString() + "]");
14577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        writeLineToControlConnection(buffer.toString());
14677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
14777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
14877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
14977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.core.session.Session#openDataConnection()
15077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
15177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public void openDataConnection() {
15277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        try {
15377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            if (passiveModeDataSocket != null) {
15477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                LOG.debug("Waiting for (passive mode) client connection from client host [" + clientHost
15577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                        + "] on port " + passiveModeDataSocket.getLocalPort());
15677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                // TODO set socket timeout
15777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                try {
15877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    dataSocket = passiveModeDataSocket.accept();
15977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    LOG.debug("Successful (passive mode) client connection to port "
16077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                            + passiveModeDataSocket.getLocalPort());
16177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                }
16277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                catch (SocketTimeoutException e) {
16377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    throw new MockFtpServerException(e);
16477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                }
16577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            } else {
16677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                Assert.notNull(clientHost, "clientHost");
16777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                LOG.debug("Connecting to client host [" + clientHost + "] on data port [" + clientDataPort
16877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                        + "]");
16977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                dataSocket = socketFactory.createSocket(clientHost, clientDataPort);
17077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            }
17177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            dataOutputStream = dataSocket.getOutputStream();
17277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            dataInputStream = dataSocket.getInputStream();
17377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
17477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        catch (IOException e) {
17577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            throw new MockFtpServerException(e);
17677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
17777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
17877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
17977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
18077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Switch to passive mode
18177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
18277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the local port to be connected to by clients for data transfers
18377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.core.session.Session#switchToPassiveMode()
18477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
18577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public int switchToPassiveMode() {
18677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        try {
18777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            passiveModeDataSocket = serverSocketFactory.createServerSocket(0);
18877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            return passiveModeDataSocket.getLocalPort();
18977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
19077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        catch (IOException e) {
19177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            throw new MockFtpServerException("Error opening passive mode server data socket", e);
19277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
19377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
19477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
19577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
19677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.core.session.Session#closeDataConnection()
19777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
19877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public void closeDataConnection() {
19977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        try {
20077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            LOG.debug("Flushing and closing client data socket");
20177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            dataOutputStream.flush();
20277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            dataOutputStream.close();
20377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            dataInputStream.close();
20477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            dataSocket.close();
20577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
20677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        catch (IOException e) {
20777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            LOG.error("Error closing client data socket", e);
20877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
20977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
21077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
21177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
21277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Write a single line to the control connection, appending a newline
21377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
21477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param line - the line to write
21577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
21677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private void writeLineToControlConnection(String line) {
21777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        try {
21877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            controlConnectionWriter.write(line + END_OF_LINE);
21977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            controlConnectionWriter.flush();
22077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
22177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        catch (IOException e) {
22277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            LOG.error("Error writing to control connection", e);
22377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            throw new MockFtpServerException("Error writing to control connection", e);
22477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
22577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
22677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
22777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
22877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.core.session.Session#close()
22977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
23077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public void close() {
23177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        LOG.trace("close()");
23277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        terminate = true;
23377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
23477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
23577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
23677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.core.session.Session#sendData(byte[], int)
23777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
23877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public void sendData(byte[] data, int numBytes) {
23977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.notNull(data, "data");
24077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        try {
24177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            dataOutputStream.write(data, 0, numBytes);
24277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
24377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        catch (IOException e) {
24477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            throw new MockFtpServerException(e);
24577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
24677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
24777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
24877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
24977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.core.session.Session#readData()
25077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
25177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public byte[] readData() {
25277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
25377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
25477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
25577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        try {
25677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            while (true) {
25777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                int b = dataInputStream.read();
25877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                if (b == -1) {
25977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    break;
26077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                }
26177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                bytes.write(b);
26277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            }
26377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            return bytes.toByteArray();
26477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
26577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        catch (IOException e) {
26677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            throw new MockFtpServerException(e);
26777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
26877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
26977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
27077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
27177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Wait for and read the command sent from the client on the control connection.
27277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
27377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the Command sent from the client; may be null if the session has been closed
27477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *         <p/>
27577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *         Package-private to enable testing
27677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
27777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    Command readCommand() {
27877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
27977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        final long socketReadIntervalMilliseconds = 100L;
28077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
28177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        try {
28277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            while (true) {
28377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                if (terminate) {
28477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    return null;
28577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                }
28677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                // Don't block; only read command when it is available
28777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                if (controlConnectionReader.ready()) {
28877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    String command = controlConnectionReader.readLine();
28977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    LOG.info("Received command: [" + command + "]");
29077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    return parseCommand(command);
29177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                }
29277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                try {
29377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    Thread.sleep(socketReadIntervalMilliseconds);
29477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                }
29577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                catch (InterruptedException e) {
29677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    throw new MockFtpServerException(e);
29777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                }
29877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            }
29977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
30077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        catch (IOException e) {
30177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            LOG.error("Read failed", e);
30277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            throw new MockFtpServerException(e);
30377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
30477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
30577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
30677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
30777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Parse the command String into a Command object
30877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
30977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param commandString - the command String
31077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the Command object parsed from the command String
31177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
31277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    Command parseCommand(String commandString) {
31377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.notNullOrEmpty(commandString, "commandString");
31477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
31577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        List parameters = new ArrayList();
31677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        String name;
31777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
31877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        int indexOfFirstSpace = commandString.indexOf(" ");
31977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (indexOfFirstSpace != -1) {
32077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            name = commandString.substring(0, indexOfFirstSpace);
32177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            StringTokenizer tokenizer = new StringTokenizer(commandString.substring(indexOfFirstSpace + 1),
32277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    ",");
32377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            while (tokenizer.hasMoreTokens()) {
32477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                parameters.add(tokenizer.nextToken());
32577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            }
32677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        } else {
32777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            name = commandString;
32877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
32977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
33077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        String[] parametersArray = new String[parameters.size()];
33177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return new Command(name, (String[]) parameters.toArray(parametersArray));
33277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
33377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
33477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
33577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.core.session.Session#setClientDataHost(java.net.InetAddress)
33677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
33777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public void setClientDataHost(InetAddress clientHost) {
33877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        this.clientHost = clientHost;
33977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
34077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
34177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
34277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.core.session.Session#setClientDataPort(int)
34377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
34477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public void setClientDataPort(int dataPort) {
34577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        this.clientDataPort = dataPort;
34677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
34777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        // Clear out any passive data connection mode information
34877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (passiveModeDataSocket != null) {
34977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            try {
35077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                this.passiveModeDataSocket.close();
35177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            }
35277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            catch (IOException e) {
35377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                throw new MockFtpServerException(e);
35477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            }
35577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            passiveModeDataSocket = null;
35677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
35777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
35877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
35977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
36077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see java.lang.Runnable#run()
36177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
36277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public void run() {
36377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        try {
36477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
36577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            InputStream inputStream = controlSocket.getInputStream();
36677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            OutputStream outputStream = controlSocket.getOutputStream();
36777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            controlConnectionReader = new BufferedReader(new InputStreamReader(inputStream));
36877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            controlConnectionWriter = new PrintWriter(outputStream, true);
36977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
37077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            LOG.debug("Starting the session...");
37177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
37277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            CommandHandler connectCommandHandler = (CommandHandler) commandHandlers.get(CommandNames.CONNECT);
37377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            connectCommandHandler.handleCommand(new Command(CommandNames.CONNECT, new String[0]), this);
37477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
37577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            while (!terminate) {
37677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                readAndProcessCommand();
37777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            }
37877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
37977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        catch (Exception e) {
38077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            LOG.error(e);
38177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            throw new MockFtpServerException(e);
38277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
38377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        finally {
38477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            LOG.debug("Cleaning up the session");
38577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            try {
38677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                controlConnectionReader.close();
38777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                controlConnectionWriter.close();
38877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            }
38977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            catch (IOException e) {
39077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                LOG.error(e);
39177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            }
39277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            LOG.debug("Session stopped.");
39377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
39477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
39577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
39677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
39777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Read and process the next command from the control connection
39877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
39977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @throws Exception - if any error occurs
40077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
40177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private void readAndProcessCommand() throws Exception {
40277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
40377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Command command = readCommand();
40477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (command != null) {
40577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            String normalizedCommandName = Command.normalizeName(command.getName());
40677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            CommandHandler commandHandler = (CommandHandler) commandHandlers.get(normalizedCommandName);
40777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
40877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            if (commandHandler == null) {
40977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                commandHandler = (CommandHandler) commandHandlers.get(CommandNames.UNSUPPORTED);
41077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            }
41177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
41277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            Assert.notNull(commandHandler, "CommandHandler for command [" + normalizedCommandName + "]");
41377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            commandHandler.handleCommand(command, this);
41477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
41577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
41677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
41777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
41877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Assert that the specified number is a valid reply code
41977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
42077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param replyCode - the reply code to check
42177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
42277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private void assertValidReplyCode(int replyCode) {
42377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.isTrue(replyCode > 0, "The number [" + replyCode + "] is not a valid reply code");
42477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
42577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
42677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
42777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return the attribute value for the specified name. Return null if no attribute value
42877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * exists for that name or if the attribute value is null.
42977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
43077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param name - the attribute name; may not be null
43177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the value of the attribute stored under name; may be null
43277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.core.session.Session#getAttribute(java.lang.String)
43377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
43477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public Object getAttribute(String name) {
43577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.notNull(name, "name");
43677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return attributes.get(name);
43777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
43877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
43977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
44077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Store the value under the specified attribute name.
44177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
44277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param name  - the attribute name; may not be null
44377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param value - the attribute value; may be null
44477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.core.session.Session#setAttribute(java.lang.String, java.lang.Object)
44577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
44677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public void setAttribute(String name, Object value) {
44777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.notNull(name, "name");
44877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        attributes.put(name, value);
44977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
45077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
45177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
45277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return the Set of names under which attributes have been stored on this session.
45377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Returns an empty Set if no attribute values are stored.
45477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
45577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the Set of attribute names
45677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.core.session.Session#getAttributeNames()
45777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
45877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public Set getAttributeNames() {
45977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return attributes.keySet();
46077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
46177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
46277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
46377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Remove the attribute value for the specified name. Do nothing if no attribute
46477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * value is stored for the specified name.
46577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
46677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param name - the attribute name; may not be null
46777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @throws AssertFailedException - if name is null
46877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.core.session.Session#removeAttribute(java.lang.String)
46977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
47077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public void removeAttribute(String name) {
47177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.notNull(name, "name");
47277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        attributes.remove(name);
47377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
47477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
47577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair}
476