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