1e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair/*
2e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair * Copyright 2007 the original author or authors.
3e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair *
4e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair * Licensed under the Apache License, Version 2.0 (the "License");
5e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair * you may not use this file except in compliance with the License.
6e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair * You may obtain a copy of the License at
7e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair *
8e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair *      http://www.apache.org/licenses/LICENSE-2.0
9e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair *
10e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair * Unless required by applicable law or agreed to in writing, software
11e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair * distributed under the License is distributed on an "AS IS" BASIS,
12e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair * See the License for the specific language governing permissions and
14e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair * limitations under the License.
15e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair */
16e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairpackage org.mockftpserver.core.session;
17e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
18e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport java.io.BufferedReader;
19e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport java.io.ByteArrayOutputStream;
20e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport java.io.IOException;
21e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport java.io.InputStream;
22e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport java.io.InputStreamReader;
23e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport java.io.OutputStream;
24e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport java.io.PrintWriter;
25e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport java.io.Writer;
26e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport java.net.InetAddress;
27e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport java.net.ServerSocket;
28e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport java.net.Socket;
29e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport java.net.SocketTimeoutException;
30e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport java.util.ArrayList;
31e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport java.util.HashMap;
32e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport java.util.List;
33e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport java.util.Map;
34e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport java.util.Set;
35e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport java.util.StringTokenizer;
36e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
37e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport org.apache.log4j.Logger;
38e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport org.mockftpserver.core.MockFtpServerException;
39e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport org.mockftpserver.core.command.Command;
40e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport org.mockftpserver.core.command.CommandHandler;
41e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport org.mockftpserver.core.command.CommandNames;
42e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport org.mockftpserver.core.socket.DefaultServerSocketFactory;
43e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport org.mockftpserver.core.socket.DefaultSocketFactory;
44e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport org.mockftpserver.core.socket.ServerSocketFactory;
45e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport org.mockftpserver.core.socket.SocketFactory;
46e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport org.mockftpserver.core.util.Assert;
47e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairimport org.mockftpserver.core.util.AssertFailedException;
48e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
49e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair/**
50e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair * Default implementation of the {@link Session} interface.
51e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair *
52e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair * @version $Revision$ - $Date$
53e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair *
54e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair * @author Chris Mair
55e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair */
56e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismairpublic class DefaultSession implements Session {
57e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
58e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    private static final Logger LOG = Logger.getLogger(DefaultSession.class);
59e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    static final int DEFAULT_CLIENT_DATA_PORT = 21;
60e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
61e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    SocketFactory socketFactory = new DefaultSocketFactory();
62e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    ServerSocketFactory serverSocketFactory = new DefaultServerSocketFactory();
63e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
64e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    private BufferedReader controlConnectionReader;
65e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    private Writer controlConnectionWriter;
66e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    private Socket controlSocket;
67e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    private Socket dataSocket;
68e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    ServerSocket passiveModeDataSocket; // non-private for testing
69e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    private InputStream dataInputStream;
70e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    private OutputStream dataOutputStream;
71e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    private Map commandHandlers;
72e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    private int clientDataPort = DEFAULT_CLIENT_DATA_PORT;
73e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    private InetAddress clientHost;
74e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    private InetAddress serverHost;
75e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    private Map attributes = new HashMap();
76e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    private volatile boolean terminate = false;
77e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
78e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
79e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * Create a new initialized instance
80e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *
81e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @param controlSocket - the control connection socket
82e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @param commandHandlers - the Map of command name -> CommandHandler. It is assumed that the
83e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *      command names are all normalized to upper case. See {@link Command#normalizeName(String)}.
84e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
85e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    public DefaultSession(Socket controlSocket, Map commandHandlers) {
86e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        Assert.notNull(controlSocket, "controlSocket");
87e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        Assert.notNull(commandHandlers, "commandHandlers");
88e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
89e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        this.controlSocket = controlSocket;
90e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        this.commandHandlers = commandHandlers;
91e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        this.serverHost = controlSocket.getLocalAddress();
92e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
93e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
94e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
95e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * Return the InetAddress representing the client host for this session
96e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *
97e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @return the client host
98e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *
99e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @see org.mockftpserver.core.session.Session#getClientHost()
100e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
101e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    public InetAddress getClientHost() {
102e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        return controlSocket.getInetAddress();
103e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
104e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
105e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
106e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * Return the InetAddress representing the server host for this session
107e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *
108e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @return the server host
109e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *
110e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @see org.mockftpserver.core.session.Session#getServerHost()
111e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
112e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    public InetAddress getServerHost() {
113e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        return serverHost;
114e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
115e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
116e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
117e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * Send the specified reply code and text across the control connection.
118e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * The reply text is trimmed before being sent.
119e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *
120e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @param code - the reply code
121e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @param text - the reply text to send; may be null
122e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
123e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    public void sendReply(int code, String text) {
124e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        assertValidReplyCode(code);
125e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
126e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        StringBuffer buffer = new StringBuffer(Integer.toString(code));
127e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
128e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        if (text != null && text.length() > 0) {
129e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            String replyText = text.trim();
130e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            if (replyText.indexOf("\n") != -1) {
131e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                int lastIndex = replyText.lastIndexOf("\n");
132e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                buffer.append("-");
133e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                for (int i = 0; i < replyText.length(); i++) {
134e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                    char c = replyText.charAt(i);
135e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                    buffer.append(c);
136e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                    if (i == lastIndex) {
137e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                        buffer.append(Integer.toString(code) + " ");
138e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                    }
139e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                }
140e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            }
141e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            else {
142e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                buffer.append(" ");
143e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                buffer.append(replyText);
144e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            }
145e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
146e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        LOG.debug("Sending Reply [" + buffer.toString() + "]");
147e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        writeLineToControlConnection(buffer.toString());
148e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
149e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
150e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
151e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @see org.mockftpserver.core.session.Session#openDataConnection()
152e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
153e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    public void openDataConnection() {
154e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        try {
155e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            if (passiveModeDataSocket != null) {
156e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                LOG.debug("Waiting for (passive mode) client connection from client host [" + clientHost
157e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                        + "] on port " + passiveModeDataSocket.getLocalPort());
158e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                // TODO set socket timeout
159e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                try {
160e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                    dataSocket = passiveModeDataSocket.accept();
161e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                    LOG.debug("Successful (passive mode) client connection to port "
162e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                            + passiveModeDataSocket.getLocalPort());
163e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                }
164e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                catch (SocketTimeoutException e) {
165e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                    throw new MockFtpServerException(e);
166e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                }
167e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            }
168e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            else {
169e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                Assert.notNull(clientHost, "clientHost");
170e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                LOG.debug("Connecting to client host [" + clientHost + "] on data port [" + clientDataPort
171e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                        + "]");
172e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                dataSocket = socketFactory.createSocket(clientHost, clientDataPort);
173e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            }
174e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            dataOutputStream = dataSocket.getOutputStream();
175e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            dataInputStream = dataSocket.getInputStream();
176e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
177e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        catch (IOException e) {
178e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            throw new MockFtpServerException(e);
179e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
180e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
181e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
182e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
183e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * Switch to passive mode
184e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *
185e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @return the local port to be connected to by clients for data transfers
186e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *
187e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @see org.mockftpserver.core.session.Session#switchToPassiveMode()
188e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
189e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    public int switchToPassiveMode() {
190e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        try {
191e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            passiveModeDataSocket = serverSocketFactory.createServerSocket(0);
192e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            return passiveModeDataSocket.getLocalPort();
193e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
194e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        catch (IOException e) {
195e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            throw new MockFtpServerException("Error opening passive mode server data socket", e);
196e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
197e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
198e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
199e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
200e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @see org.mockftpserver.core.session.Session#closeDataConnection()
201e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
202e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    public void closeDataConnection() {
203e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        try {
204e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            LOG.debug("Flushing and closing client data socket");
205e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            dataOutputStream.flush();
206e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            dataOutputStream.close();
207e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            dataInputStream.close();
208e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            dataSocket.close();
209e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
210e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        catch (IOException e) {
211e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            LOG.error("Error closing client data socket", e);
212e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
213e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
214e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
215e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
216e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * Write a single line to the control connection, appending a newline
217e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *
218e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @param line - the line to write
219e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
220e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    private void writeLineToControlConnection(String line) {
221e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        try {
222e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            controlConnectionWriter.write(line + "\n");
223e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            controlConnectionWriter.flush();
224e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
225e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        catch (IOException e) {
226e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            LOG.error("Error writing to control connection", e);
227e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            throw new MockFtpServerException("Error writing to control connection", e);
228e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
229e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
230e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
231e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
232e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @see org.mockftpserver.core.session.Session#close()
233e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
234e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    public void close() {
235e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        LOG.trace("close()");
236e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        terminate = true;
237e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
238e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
239e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
240e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @see org.mockftpserver.core.session.Session#sendData(byte[], int)
241e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
242e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    public void sendData(byte[] data, int numBytes) {
243e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        Assert.notNull(data, "data");
244e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        try {
245e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            dataOutputStream.write(data, 0, numBytes);
246e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
247e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        catch (IOException e) {
248e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            throw new MockFtpServerException(e);
249e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
250e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
251e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
252e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
253e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @see org.mockftpserver.core.session.Session#readData()
254e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
255e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    public byte[] readData() {
256e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
257e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
258e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
259e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        try {
260e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            while (true) {
261e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                int b = dataInputStream.read();
262e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                if (b == -1) {
263e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                    break;
264e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                }
265e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                bytes.write(b);
266e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            }
267e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            return bytes.toByteArray();
268e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
269e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        catch (IOException e) {
270e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            throw new MockFtpServerException(e);
271e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
272e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
273e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
274e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
275e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * Wait for and read the command sent from the client on the control connection.
276e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *
277e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @return the Command sent from the client; may be null if the session has been closed
278e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *
279e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * Package-private to enable testing
280e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
281e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    Command readCommand() {
282e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
283e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        final long socketReadIntervalMilliseconds = 100L;
284e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
285e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        try {
286e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            while (true) {
287e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                if (terminate) {
288e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                    return null;
289e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                }
290e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                // Don't block; only read command when it is available
291e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                if (controlConnectionReader.ready()) {
292e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                    String command = controlConnectionReader.readLine();
293e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                    LOG.info("Received command: [" + command + "]");
294e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                    return parseCommand(command);
295e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                }
296e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                try {
297e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                    Thread.sleep(socketReadIntervalMilliseconds);
298e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                }
299e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                catch (InterruptedException e) {
300e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                    throw new MockFtpServerException(e);
301e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                }
302e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            }
303e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
304e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        catch (IOException e) {
305e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            LOG.error("Read failed", e);
306e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            throw new MockFtpServerException(e);
307e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
308e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
309e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
310e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
311e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * Parse the command String into a Command object
312e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *
313e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @param commandString - the command String
314e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @return the Command object parsed from the command String
315e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
316e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    Command parseCommand(String commandString) {
317e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        Assert.notNullOrEmpty(commandString, "commandString");
318e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
319e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        List parameters = new ArrayList();
320e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        String name;
321e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
322e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        int indexOfFirstSpace = commandString.indexOf(" ");
323e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        if (indexOfFirstSpace != -1) {
324e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            name = commandString.substring(0, indexOfFirstSpace);
325e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            StringTokenizer tokenizer = new StringTokenizer(commandString.substring(indexOfFirstSpace + 1),
326e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                    ",");
327e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            while (tokenizer.hasMoreTokens()) {
328e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                parameters.add(tokenizer.nextToken());
329e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            }
330e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
331e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        else {
332e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            name = commandString;
333e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
334e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
335e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        String[] parametersArray = new String[parameters.size()];
336e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        return new Command(name, (String[]) parameters.toArray(parametersArray));
337e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
338e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
339e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
340e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @see org.mockftpserver.core.session.Session#setClientDataHost(java.net.InetAddress)
341e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
342e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    public void setClientDataHost(InetAddress clientHost) {
343e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        this.clientHost = clientHost;
344e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
345e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
346e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
347e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @see org.mockftpserver.core.session.Session#setClientDataPort(int)
348e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
349e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    public void setClientDataPort(int dataPort) {
350e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        this.clientDataPort = dataPort;
351e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
352e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        // Clear out any passive data connection mode information
353e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        if (passiveModeDataSocket != null) {
354e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            try {
355e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                this.passiveModeDataSocket.close();
356e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            }
357e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            catch (IOException e) {
358e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                throw new MockFtpServerException(e);
359e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            }
360e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            passiveModeDataSocket = null;
361e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
362e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
363e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
364e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
365e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @see java.lang.Runnable#run()
366e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
367e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    public void run() {
368e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        try {
369e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
370e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            InputStream inputStream = controlSocket.getInputStream();
371e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            OutputStream outputStream = controlSocket.getOutputStream();
372e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            controlConnectionReader = new BufferedReader(new InputStreamReader(inputStream));
373e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            controlConnectionWriter = new PrintWriter(outputStream, true);
374e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
375e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            LOG.debug("Starting the session...");
376e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
377e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            CommandHandler connectCommandHandler = (CommandHandler) commandHandlers.get(CommandNames.CONNECT);
378e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            connectCommandHandler.handleCommand(new Command(CommandNames.CONNECT, new String[0]), this);
379e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
380e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            while (!terminate) {
381e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                readAndProcessCommand();
382e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            }
383e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
384e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        catch (Exception e) {
385e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            LOG.error(e);
386e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            throw new MockFtpServerException(e);
387e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
388e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        finally {
389e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            LOG.debug("Cleaning up the session");
390e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            try {
391e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                controlConnectionReader.close();
392e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                controlConnectionWriter.close();
393e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            }
394e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            catch (IOException e) {
395e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                LOG.error(e);
396e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair                throw new MockFtpServerException(e);
397e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            }
398e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            LOG.debug("Session stopped.");
399e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
400e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
401e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
402e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
403e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * Read and process the next command from the control connection
404e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *
405e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @throws Exception
406e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
407e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    private void readAndProcessCommand() throws Exception {
408e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
409e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        Command command = readCommand();
410e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        if (command != null) {
411e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            String normalizedCommandName = Command.normalizeName(command.getName());
412e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            CommandHandler commandHandler = (CommandHandler) commandHandlers.get(normalizedCommandName);
413e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            Assert.notNull(commandHandler, "CommandHandler for command [" + normalizedCommandName + "]");
414e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair            commandHandler.handleCommand(command, this);
415e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        }
416e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
417e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
418e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
419e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * Assert that the specified number is a valid reply code
420e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *
421e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @param replyCode - the reply code to check
422e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
423e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    private void assertValidReplyCode(int replyCode) {
424e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        Assert.isTrue(replyCode > 0, "The number [" + replyCode + "] is not a valid reply code");
425e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
426e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
427e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
428e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * Return the attribute value for the specified name. Return null if no attribute value
429e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * exists for that name or if the attribute value is null.
430e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @param name - the attribute name; may not be null
431e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @return the value of the attribute stored under name; may be null
432e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *
433e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @see org.mockftpserver.core.session.Session#getAttribute(java.lang.String)
434e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
435e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    public Object getAttribute(String name) {
436e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        Assert.notNull(name, "name");
437e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        return attributes.get(name);
438e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
439e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
440e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
441e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * Store the value under the specified attribute name.
442e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @param name - the attribute name; may not be null
443e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @param value - the attribute value; may be null
444e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *
445e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @see org.mockftpserver.core.session.Session#setAttribute(java.lang.String, java.lang.Object)
446e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
447e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    public void setAttribute(String name, Object value) {
448e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        Assert.notNull(name, "name");
449e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        attributes.put(name, value);
450e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
451e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
452e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
453e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * Return the Set of names under which attributes have been stored on this session.
454e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * Returns an empty Set if no attribute values are stored.
455e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @return the Set of attribute names
456e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *
457e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @see org.mockftpserver.core.session.Session#getAttributeNames()
458e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
459e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    public Set getAttributeNames() {
460e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        return attributes.keySet();
461e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
462e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
463e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    /**
464e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * Remove the attribute value for the specified name. Do nothing if no attribute
465e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * value is stored for the specified name.
466e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @param name - the attribute name; may not be null
467e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @throws AssertFailedException - if name is null
468e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     *
469e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     * @see org.mockftpserver.core.session.Session#removeAttribute(java.lang.String)
470e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair     */
471e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    public void removeAttribute(String name) {
472e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        Assert.notNull(name, "name");
473e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair        attributes.remove(name);
474e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair    }
475e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair
476e47352fb2508e2b25f003b8df12fa79c3215b4b1chrismair}
477