1a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair/*
2a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * Copyright 2008 the original author or authors.
3a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair *
4a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * Licensed under the Apache License, Version 2.0 (the "License");
5a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * you may not use this file except in compliance with the License.
6a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * You may obtain a copy of the License at
7a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair *
8a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair *      http://www.apache.org/licenses/LICENSE-2.0
9a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair *
10a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * Unless required by applicable law or agreed to in writing, software
11a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * distributed under the License is distributed on an "AS IS" BASIS,
12a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * See the License for the specific language governing permissions and
14a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * limitations under the License.
15a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair */
16a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairpackage org.mockftpserver.fake.command;
17a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
18a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.core.CommandSyntaxException;
19a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.core.IllegalStateException;
20a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.core.NotLoggedInException;
21a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.core.command.AbstractCommandHandler;
22a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.core.command.Command;
23a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.core.command.ReplyCodes;
24a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.core.session.Session;
25a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.core.session.SessionKeys;
26a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.core.util.Assert;
27a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.fake.ServerConfiguration;
28a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.fake.ServerConfigurationAware;
29a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.fake.UserAccount;
30a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.fake.filesystem.FileSystem;
31a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.fake.filesystem.FileSystemEntry;
32a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.fake.filesystem.FileSystemException;
33a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.fake.filesystem.InvalidFilenameException;
34a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
35a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport java.text.MessageFormat;
36a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport java.util.ArrayList;
37a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport java.util.Collections;
38a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport java.util.List;
39a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport java.util.MissingResourceException;
40a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
41a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair/**
42a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * Abstract superclass for CommandHandler classes for the "Fake" server.
43a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair *
44a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * @author Chris Mair
45a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * @version $Revision$ - $Date$
46a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair */
47a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairpublic abstract class AbstractFakeCommandHandler extends AbstractCommandHandler implements ServerConfigurationAware {
48a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
49a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected static final String INTERNAL_ERROR_KEY = "internalError";
50a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
51a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    private ServerConfiguration serverConfiguration;
52a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
53a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
54a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Reply code sent back when a FileSystemException is caught by the                 {@link #handleCommand(Command, Session)}
55a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * This defaults to ReplyCodes.EXISTING_FILE_ERROR (550).
56a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
57a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected int replyCodeForFileSystemException = ReplyCodes.READ_FILE_ERROR;
58a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
59a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public ServerConfiguration getServerConfiguration() {
60a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return serverConfiguration;
61a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
62a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
63a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public void setServerConfiguration(ServerConfiguration serverConfiguration) {
64a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        this.serverConfiguration = serverConfiguration;
65a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
66a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
67a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
68a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Use template method to centralize and ensure common validation
69a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
70a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public void handleCommand(Command command, Session session) {
71a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Assert.notNull(serverConfiguration, "serverConfiguration");
72a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Assert.notNull(command, "command");
73a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Assert.notNull(session, "session");
74a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
75a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        try {
76a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            handle(command, session);
77a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
78a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        catch (CommandSyntaxException e) {
79a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            handleException(command, session, e, ReplyCodes.COMMAND_SYNTAX_ERROR);
80a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
81a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        catch (IllegalStateException e) {
82a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            handleException(command, session, e, ReplyCodes.ILLEGAL_STATE);
83a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
84a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        catch (NotLoggedInException e) {
85a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            handleException(command, session, e, ReplyCodes.NOT_LOGGED_IN);
86a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
87a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        catch (InvalidFilenameException e) {
88a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            handleFileSystemException(command, session, e, ReplyCodes.FILENAME_NOT_VALID, list(e.getPath()));
89a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
90a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        catch (FileSystemException e) {
91a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            handleFileSystemException(command, session, e, replyCodeForFileSystemException, list(e.getPath()));
92a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
93a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
94a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
95a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
96a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Convenience method to return the FileSystem stored in the ServerConfiguration
97a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
98a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the FileSystem
99a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
100a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected FileSystem getFileSystem() {
101a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return serverConfiguration.getFileSystem();
102a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
103a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
104a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
105a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Handle the specified command for the session. All checked exceptions are expected to be wrapped or handled
106a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * by the caller.
107a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
108a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param command - the Command to be handled
109a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param session - the session on which the Command was submitted
110a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
111a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected abstract void handle(Command command, Session session);
112a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
113a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    // -------------------------------------------------------------------------
114a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    // Utility methods for subclasses
115a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    // -------------------------------------------------------------------------
116a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
117a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
118a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Send a reply for this command on the control connection.
119a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * <p/>
120a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * The reply code is designated by the <code>replyCode</code> property, and the reply text
121a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * is retrieved from the <code>replyText</code> ResourceBundle, using the specified messageKey.
122a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
123a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param session    - the Session
124a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param replyCode  - the reply code
125a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param messageKey - the resource bundle key for the reply text
126a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws AssertionError - if session is null
127a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @see MessageFormat
128a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
129a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected void sendReply(Session session, int replyCode, String messageKey) {
130a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        sendReply(session, replyCode, messageKey, Collections.EMPTY_LIST);
131a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
132a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
133a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
134a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Send a reply for this command on the control connection.
135a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * <p/>
136a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * The reply code is designated by the <code>replyCode</code> property, and the reply text
137a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * is retrieved from the <code>replyText</code> ResourceBundle, using the specified messageKey.
138a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
139a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param session    - the Session
140a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param replyCode  - the reply code
141a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param messageKey - the resource bundle key for the reply text
142a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param args       - the optional message arguments; defaults to []
143a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws AssertionError - if session is null
144a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @see MessageFormat
145a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
146a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected void sendReply(Session session, int replyCode, String messageKey, List args) {
147a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Assert.notNull(session, "session");
148a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        assertValidReplyCode(replyCode);
149a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
150a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String text = getTextForKey(messageKey);
151a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String replyText = (args != null && !args.isEmpty()) ? MessageFormat.format(text, args.toArray()) : text;
152a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
153a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String replyTextToLog = (replyText == null) ? "" : " " + replyText;
154a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String argsToLog = (args != null && !args.isEmpty()) ? (" args=" + args) : "";
155a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        LOG.info("Sending reply [" + replyCode + replyTextToLog + "]" + argsToLog);
156a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        session.sendReply(replyCode, replyText);
157a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
158a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
159a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
160a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Send a reply for this command on the control connection.
161a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * <p/>
162a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * The reply code is designated by the <code>replyCode</code> property, and the reply text
163a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * is retrieved from the <code>replyText</code> ResourceBundle, using the reply code as the key.
164a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
165a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param session   - the Session
166a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param replyCode - the reply code
167a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws AssertionError - if session is null
168a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @see MessageFormat
169a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
170a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected void sendReply(Session session, int replyCode) {
171a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        sendReply(session, replyCode, Collections.EMPTY_LIST);
172a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
173a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
174a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
175a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Send a reply for this command on the control connection.
176a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * <p/>
177a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * The reply code is designated by the <code>replyCode</code> property, and the reply text
178a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * is retrieved from the <code>replyText</code> ResourceBundle, using the reply code as the key.
179a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
180a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param session   - the Session
181a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param replyCode - the reply code
182a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param args      - the optional message arguments; defaults to []
183a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws AssertionError - if session is null
184a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @see MessageFormat
185a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
186a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected void sendReply(Session session, int replyCode, List args) {
187a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        sendReply(session, replyCode, Integer.toString(replyCode), args);
188a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
189a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
190a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
191a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Handle the exception caught during handleCommand()
192a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
193a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param command   - the Command
194a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param session   - the Session
195a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param exception - the caught exception
196a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param replyCode - the reply code that should be sent back
197a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
198a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    private void handleException(Command command, Session session, Throwable exception, int replyCode) {
199a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        LOG.warn("Error handling command: " + command + "; " + exception, exception);
200a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        sendReply(session, replyCode);
201a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
202a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
203a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
204a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Handle the exception caught during handleCommand()
205a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
206a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param command   - the Command
207a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param session   - the Session
208a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param exception - the caught exception
209a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param replyCode - the reply code that should be sent back
210a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param arg       - the arg for the reply (message)
211a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
212a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    private void handleFileSystemException(Command command, Session session, FileSystemException exception, int replyCode, Object arg) {
213a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        LOG.warn("Error handling command: " + command + "; " + exception, exception);
214a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        sendReply(session, replyCode, exception.getMessageKey(), Collections.singletonList(arg));
215a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
216a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
217a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
218a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return the value of the named attribute within the session.
219a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
220a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param session - the Session
221a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param name    - the name of the session attribute to retrieve
222a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the value of the named session attribute
223a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws IllegalStateException - if the Session does not contain the named attribute
224a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
225a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected Object getRequiredSessionAttribute(Session session, String name) {
226a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Object value = session.getAttribute(name);
227a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (value == null) {
228a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            throw new IllegalStateException("Session missing required attribute [" + name + "]");
229a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
230a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return value;
231a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
232a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
233a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
234a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Verify that the current user (if any) has already logged in successfully.
235a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
236a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param session - the Session
237a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
238a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected void verifyLoggedIn(Session session) {
239a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (getUserAccount(session) == null) {
240a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            throw new NotLoggedInException("User has not logged in");
241a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
242a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
243a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
244a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
245a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param session - the Session
246a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the UserAccount stored in the specified session; may be null
247a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
248a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected UserAccount getUserAccount(Session session) {
249a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return (UserAccount) session.getAttribute(SessionKeys.USER_ACCOUNT);
250a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
251a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
252a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
253a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Verify that the specified condition related to the file system is true,
254a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * otherwise throw a FileSystemException.
255a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
256a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param condition  - the condition that must be true
257a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path       - the path involved in the operation; this will be included in the
258a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *                   error message if the condition is not true.
259a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param messageKey - the message key for the exception message
260a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws FileSystemException - if the condition is not true
261a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
262a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected void verifyFileSystemCondition(boolean condition, String path, String messageKey) {
263a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (!condition) {
264a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            throw new FileSystemException(path, messageKey);
265a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
266a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
267a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
268a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
269a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Verify that the current user has execute permission to the specified path
270a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
271a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param session - the Session
272a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path    - the file system path
273a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws FileSystemException - if the condition is not true
274a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
275a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected void verifyExecutePermission(Session session, String path) {
276a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        UserAccount userAccount = getUserAccount(session);
277a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        FileSystemEntry entry = getFileSystem().getEntry(path);
278a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        verifyFileSystemCondition(userAccount.canExecute(entry), path, "filesystem.cannotExecute");
279a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
280a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
281a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
282a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Verify that the current user has write permission to the specified path
283a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
284a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param session - the Session
285a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path    - the file system path
286a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws FileSystemException - if the condition is not true
287a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
288a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected void verifyWritePermission(Session session, String path) {
289a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        UserAccount userAccount = getUserAccount(session);
290a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        FileSystemEntry entry = getFileSystem().getEntry(path);
291a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        verifyFileSystemCondition(userAccount.canWrite(entry), path, "filesystem.cannotWrite");
292a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
293a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
294a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
295a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Verify that the current user has read permission to the specified path
296a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
297a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param session - the Session
298a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path    - the file system path
299a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws FileSystemException - if the condition is not true
300a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
301a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected void verifyReadPermission(Session session, String path) {
302a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        UserAccount userAccount = getUserAccount(session);
303a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        FileSystemEntry entry = getFileSystem().getEntry(path);
304a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        verifyFileSystemCondition(userAccount.canRead(entry), path, "filesystem.cannotRead");
305a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
306a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
307a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
308a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return the full, absolute path for the specified abstract pathname.
309a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * If path is null, return the current directory (stored in the session). If
310a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * path represents an absolute path, then return path as is. Otherwise, path
311a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * is relative, so assemble the full path from the current directory
312a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * and the specified relative path.
313a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
314a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param session - the Session
315a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path    - the abstract pathname; may be null
316a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the resulting full, absolute path
317a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
318a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected String getRealPath(Session session, String path) {
319a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String currentDirectory = (String) session.getAttribute(SessionKeys.CURRENT_DIRECTORY);
320a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (path == null) {
321a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            return currentDirectory;
322a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
323a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (getFileSystem().isAbsolute(path)) {
324a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            return path;
325a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
326a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return getFileSystem().path(currentDirectory, path);
327a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
328a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
329a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
330a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return the end-of-line character(s) used when building multi-line responses
331a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
332a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return "\r\n"
333a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
334a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected String endOfLine() {
335a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return "\r\n";
336a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
337a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
338a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    private String getTextForKey(String key) {
339a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String msgKey = (key != null) ? key : INTERNAL_ERROR_KEY;
340a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        try {
341a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            return getReplyTextBundle().getString(msgKey);
342a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
343a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        catch (MissingResourceException e) {
344a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            // No reply text is mapped for the specified key
345a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            LOG.warn("No reply text defined for key [" + msgKey + "]");
346a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            return null;
347a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
348a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
349a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
350a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    // -------------------------------------------------------------------------
351a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    // Login Support (used by USER and PASS commands)
352a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    // -------------------------------------------------------------------------
353a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
354a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
355a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Validate the UserAccount for the specified username. If valid, return true. If the UserAccount does
356a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * not exist or is invalid, log an error message, send back a reply code of 530 with an appropriate
357a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * error message, and return false. A UserAccount is considered invalid if the homeDirectory property
358a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * is not set or is set to a non-existent directory.
359a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
360a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param username - the username
361a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param session  - the session; used to send back an error reply if necessary
362a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return true only if the UserAccount for the named user is valid
363a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
364a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected boolean validateUserAccount(String username, Session session) {
365a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        UserAccount userAccount = serverConfiguration.getUserAccount(username);
366a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (userAccount == null || !userAccount.isValid()) {
367a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            LOG.error("UserAccount missing or not valid for username [" + username + "]: " + userAccount);
368a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            sendReply(session, ReplyCodes.USER_ACCOUNT_NOT_VALID, "login.userAccountNotValid", list(username));
369a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            return false;
370a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
371a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
372a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String home = userAccount.getHomeDirectory();
373a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (!getFileSystem().isDirectory(home)) {
374a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            LOG.error("Home directory configured for username [" + username + "] is not valid: " + home);
375a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            sendReply(session, ReplyCodes.USER_ACCOUNT_NOT_VALID, "login.homeDirectoryNotValid", list(username, home));
376a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            return false;
377a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
378a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
379a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return true;
380a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
381a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
382a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
383a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Log in the specified user for the current session. Send back a reply of 230 with a message indicated
384a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * by the replyMessageKey and set the UserAccount and current directory (homeDirectory) in the session.
385a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
386a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param userAccount     - the userAccount for the user to be logged in
387a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param session         - the session
388a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param replyCode       - the reply code to send
389a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param replyMessageKey - the message key for the reply text
390a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
391a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected void login(UserAccount userAccount, Session session, int replyCode, String replyMessageKey) {
392a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        sendReply(session, replyCode, replyMessageKey);
393a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        session.setAttribute(SessionKeys.USER_ACCOUNT, userAccount);
394a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        session.setAttribute(SessionKeys.CURRENT_DIRECTORY, userAccount.getHomeDirectory());
395a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
396a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
397a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
398a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Convenience method to return a List with the specified single item
399a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
400a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param item - the single item in the returned List
401a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return a new List with that single item
402a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
403a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected List list(Object item) {
404a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return Collections.singletonList(item);
405a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
406a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
407a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
408a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Convenience method to return a List with the specified two items
409a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
410a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param item1 - the first item in the returned List
411a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param item2 - the second item in the returned List
412a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return a new List with the specified items
413a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
414a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected List list(Object item1, Object item2) {
415a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        List list = new ArrayList(2);
416a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        list.add(item1);
417a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        list.add(item2);
418a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return list;
419a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
420a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
421a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
422a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return true if the specified string is null or empty
423a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
424a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param string - the String to check; may be null
425a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return true only if the specified String is null or empyt
426a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
427a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected boolean notNullOrEmpty(String string) {
428a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return string != null && string.length() > 0;
429a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
430a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
431a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
432a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return the string unless it is null or empty, in which case return the defaultString.
433a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
434a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param string        - the String to check; may be null
435a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param defaultString - the value to return if string is null or empty
436a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return string if not null and not empty; otherwise return defaultString
437a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
438a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected String defaultIfNullOrEmpty(String string, String defaultString) {
439a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return (notNullOrEmpty(string) ? string : defaultString);
440a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
441a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
442a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair}