summaryrefslogtreecommitdiff
path: root/tags/2.1/src/main/java/org/mockftpserver/fake/command/AbstractFakeCommandHandler.java
diff options
context:
space:
mode:
Diffstat (limited to 'tags/2.1/src/main/java/org/mockftpserver/fake/command/AbstractFakeCommandHandler.java')
-rw-r--r--tags/2.1/src/main/java/org/mockftpserver/fake/command/AbstractFakeCommandHandler.java442
1 files changed, 442 insertions, 0 deletions
diff --git a/tags/2.1/src/main/java/org/mockftpserver/fake/command/AbstractFakeCommandHandler.java b/tags/2.1/src/main/java/org/mockftpserver/fake/command/AbstractFakeCommandHandler.java
new file mode 100644
index 0000000..25bb23c
--- /dev/null
+++ b/tags/2.1/src/main/java/org/mockftpserver/fake/command/AbstractFakeCommandHandler.java
@@ -0,0 +1,442 @@
+/*
+ * Copyright 2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mockftpserver.fake.command;
+
+import org.mockftpserver.core.CommandSyntaxException;
+import org.mockftpserver.core.IllegalStateException;
+import org.mockftpserver.core.NotLoggedInException;
+import org.mockftpserver.core.command.AbstractCommandHandler;
+import org.mockftpserver.core.command.Command;
+import org.mockftpserver.core.command.ReplyCodes;
+import org.mockftpserver.core.session.Session;
+import org.mockftpserver.core.session.SessionKeys;
+import org.mockftpserver.core.util.Assert;
+import org.mockftpserver.fake.ServerConfiguration;
+import org.mockftpserver.fake.ServerConfigurationAware;
+import org.mockftpserver.fake.UserAccount;
+import org.mockftpserver.fake.filesystem.FileSystem;
+import org.mockftpserver.fake.filesystem.FileSystemEntry;
+import org.mockftpserver.fake.filesystem.FileSystemException;
+import org.mockftpserver.fake.filesystem.InvalidFilenameException;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.MissingResourceException;
+
+/**
+ * Abstract superclass for CommandHandler classes for the "Fake" server.
+ *
+ * @author Chris Mair
+ * @version $Revision$ - $Date$
+ */
+public abstract class AbstractFakeCommandHandler extends AbstractCommandHandler implements ServerConfigurationAware {
+
+ protected static final String INTERNAL_ERROR_KEY = "internalError";
+
+ private ServerConfiguration serverConfiguration;
+
+ /**
+ * Reply code sent back when a FileSystemException is caught by the {@link #handleCommand(Command, Session)}
+ * This defaults to ReplyCodes.EXISTING_FILE_ERROR (550).
+ */
+ protected int replyCodeForFileSystemException = ReplyCodes.READ_FILE_ERROR;
+
+ public ServerConfiguration getServerConfiguration() {
+ return serverConfiguration;
+ }
+
+ public void setServerConfiguration(ServerConfiguration serverConfiguration) {
+ this.serverConfiguration = serverConfiguration;
+ }
+
+ /**
+ * Use template method to centralize and ensure common validation
+ */
+ public void handleCommand(Command command, Session session) {
+ Assert.notNull(serverConfiguration, "serverConfiguration");
+ Assert.notNull(command, "command");
+ Assert.notNull(session, "session");
+
+ try {
+ handle(command, session);
+ }
+ catch (CommandSyntaxException e) {
+ handleException(command, session, e, ReplyCodes.COMMAND_SYNTAX_ERROR);
+ }
+ catch (IllegalStateException e) {
+ handleException(command, session, e, ReplyCodes.ILLEGAL_STATE);
+ }
+ catch (NotLoggedInException e) {
+ handleException(command, session, e, ReplyCodes.NOT_LOGGED_IN);
+ }
+ catch (InvalidFilenameException e) {
+ handleFileSystemException(command, session, e, ReplyCodes.FILENAME_NOT_VALID, e.getPath());
+ }
+ catch (FileSystemException e) {
+ handleFileSystemException(command, session, e, replyCodeForFileSystemException, e.getPath());
+ }
+ }
+
+ /**
+ * Convenience method to return the FileSystem stored in the ServerConfiguration
+ *
+ * @return the FileSystem
+ */
+ protected FileSystem getFileSystem() {
+ return serverConfiguration.getFileSystem();
+ }
+
+ /**
+ * Handle the specified command for the session. All checked exceptions are expected to be wrapped or handled
+ * by the caller.
+ *
+ * @param command - the Command to be handled
+ * @param session - the session on which the Command was submitted
+ */
+ protected abstract void handle(Command command, Session session);
+
+ // -------------------------------------------------------------------------
+ // Utility methods for subclasses
+ // -------------------------------------------------------------------------
+
+ /**
+ * Send a reply for this command on the control connection.
+ * <p/>
+ * The reply code is designated by the <code>replyCode</code> property, and the reply text
+ * is retrieved from the <code>replyText</code> ResourceBundle, using the specified messageKey.
+ *
+ * @param session - the Session
+ * @param replyCode - the reply code
+ * @param messageKey - the resource bundle key for the reply text
+ * @throws AssertionError - if session is null
+ * @see MessageFormat
+ */
+ protected void sendReply(Session session, int replyCode, String messageKey) {
+ sendReply(session, replyCode, messageKey, Collections.EMPTY_LIST);
+ }
+
+ /**
+ * Send a reply for this command on the control connection.
+ * <p/>
+ * The reply code is designated by the <code>replyCode</code> property, and the reply text
+ * is retrieved from the <code>replyText</code> ResourceBundle, using the specified messageKey.
+ *
+ * @param session - the Session
+ * @param replyCode - the reply code
+ * @param messageKey - the resource bundle key for the reply text
+ * @param args - the optional message arguments; defaults to []
+ * @throws AssertionError - if session is null
+ * @see MessageFormat
+ */
+ protected void sendReply(Session session, int replyCode, String messageKey, List args) {
+ Assert.notNull(session, "session");
+ assertValidReplyCode(replyCode);
+
+ String text = getTextForKey(messageKey);
+ String replyText = (args != null && !args.isEmpty()) ? MessageFormat.format(text, args.toArray()) : text;
+
+ String replyTextToLog = (replyText == null) ? "" : " " + replyText;
+ String argsToLog = (args != null && !args.isEmpty()) ? (" args=" + args) : "";
+ LOG.info("Sending reply [" + replyCode + replyTextToLog + "]" + argsToLog);
+ session.sendReply(replyCode, replyText);
+ }
+
+ /**
+ * Send a reply for this command on the control connection.
+ * <p/>
+ * The reply code is designated by the <code>replyCode</code> property, and the reply text
+ * is retrieved from the <code>replyText</code> ResourceBundle, using the reply code as the key.
+ *
+ * @param session - the Session
+ * @param replyCode - the reply code
+ * @throws AssertionError - if session is null
+ * @see MessageFormat
+ */
+ protected void sendReply(Session session, int replyCode) {
+ sendReply(session, replyCode, Collections.EMPTY_LIST);
+ }
+
+ /**
+ * Send a reply for this command on the control connection.
+ * <p/>
+ * The reply code is designated by the <code>replyCode</code> property, and the reply text
+ * is retrieved from the <code>replyText</code> ResourceBundle, using the reply code as the key.
+ *
+ * @param session - the Session
+ * @param replyCode - the reply code
+ * @param args - the optional message arguments; defaults to []
+ * @throws AssertionError - if session is null
+ * @see MessageFormat
+ */
+ protected void sendReply(Session session, int replyCode, List args) {
+ sendReply(session, replyCode, Integer.toString(replyCode), args);
+ }
+
+ /**
+ * Handle the exception caught during handleCommand()
+ *
+ * @param command - the Command
+ * @param session - the Session
+ * @param exception - the caught exception
+ * @param replyCode - the reply code that should be sent back
+ */
+ private void handleException(Command command, Session session, Throwable exception, int replyCode) {
+ LOG.warn("Error handling command: " + command + "; " + exception, exception);
+ sendReply(session, replyCode);
+ }
+
+ /**
+ * Handle the exception caught during handleCommand()
+ *
+ * @param command - the Command
+ * @param session - the Session
+ * @param exception - the caught exception
+ * @param replyCode - the reply code that should be sent back
+ * @param arg - the arg for the reply (message)
+ */
+ private void handleFileSystemException(Command command, Session session, FileSystemException exception, int replyCode, Object arg) {
+ LOG.warn("Error handling command: " + command + "; " + exception, exception);
+ sendReply(session, replyCode, exception.getMessageKey(), Collections.singletonList(arg));
+ }
+
+ /**
+ * Return the value of the named attribute within the session.
+ *
+ * @param session - the Session
+ * @param name - the name of the session attribute to retrieve
+ * @return the value of the named session attribute
+ * @throws IllegalStateException - if the Session does not contain the named attribute
+ */
+ protected Object getRequiredSessionAttribute(Session session, String name) {
+ Object value = session.getAttribute(name);
+ if (value == null) {
+ throw new IllegalStateException("Session missing required attribute [" + name + "]");
+ }
+ return value;
+ }
+
+ /**
+ * Verify that the current user (if any) has already logged in successfully.
+ *
+ * @param session - the Session
+ */
+ protected void verifyLoggedIn(Session session) {
+ if (getUserAccount(session) == null) {
+ throw new NotLoggedInException("User has not logged in");
+ }
+ }
+
+ /**
+ * @param session - the Session
+ * @return the UserAccount stored in the specified session; may be null
+ */
+ protected UserAccount getUserAccount(Session session) {
+ return (UserAccount) session.getAttribute(SessionKeys.USER_ACCOUNT);
+ }
+
+ /**
+ * Verify that the specified condition related to the file system is true,
+ * otherwise throw a FileSystemException.
+ *
+ * @param condition - the condition that must be true
+ * @param path - the path involved in the operation; this will be included in the
+ * error message if the condition is not true.
+ * @param messageKey - the message key for the exception message
+ * @throws FileSystemException - if the condition is not true
+ */
+ protected void verifyFileSystemCondition(boolean condition, String path, String messageKey) {
+ if (!condition) {
+ throw new FileSystemException(path, messageKey);
+ }
+ }
+
+ /**
+ * Verify that the current user has execute permission to the specified path
+ *
+ * @param session - the Session
+ * @param path - the file system path
+ * @throws FileSystemException - if the condition is not true
+ */
+ protected void verifyExecutePermission(Session session, String path) {
+ UserAccount userAccount = getUserAccount(session);
+ FileSystemEntry entry = getFileSystem().getEntry(path);
+ verifyFileSystemCondition(userAccount.canExecute(entry), path, "filesystem.cannotExecute");
+ }
+
+ /**
+ * Verify that the current user has write permission to the specified path
+ *
+ * @param session - the Session
+ * @param path - the file system path
+ * @throws FileSystemException - if the condition is not true
+ */
+ protected void verifyWritePermission(Session session, String path) {
+ UserAccount userAccount = getUserAccount(session);
+ FileSystemEntry entry = getFileSystem().getEntry(path);
+ verifyFileSystemCondition(userAccount.canWrite(entry), path, "filesystem.cannotWrite");
+ }
+
+ /**
+ * Verify that the current user has read permission to the specified path
+ *
+ * @param session - the Session
+ * @param path - the file system path
+ * @throws FileSystemException - if the condition is not true
+ */
+ protected void verifyReadPermission(Session session, String path) {
+ UserAccount userAccount = getUserAccount(session);
+ FileSystemEntry entry = getFileSystem().getEntry(path);
+ verifyFileSystemCondition(userAccount.canRead(entry), path, "filesystem.cannotRead");
+ }
+
+ /**
+ * Return the full, absolute path for the specified abstract pathname.
+ * If path is null, return the current directory (stored in the session). If
+ * path represents an absolute path, then return path as is. Otherwise, path
+ * is relative, so assemble the full path from the current directory
+ * and the specified relative path.
+ *
+ * @param session - the Session
+ * @param path - the abstract pathname; may be null
+ * @return the resulting full, absolute path
+ */
+ protected String getRealPath(Session session, String path) {
+ String currentDirectory = (String) session.getAttribute(SessionKeys.CURRENT_DIRECTORY);
+ if (path == null) {
+ return currentDirectory;
+ }
+ if (getFileSystem().isAbsolute(path)) {
+ return path;
+ }
+ return getFileSystem().path(currentDirectory, path);
+ }
+
+ /**
+ * Return the end-of-line character(s) used when building multi-line responses
+ *
+ * @return "\r\n"
+ */
+ protected String endOfLine() {
+ return "\r\n";
+ }
+
+ private String getTextForKey(String key) {
+ String msgKey = (key != null) ? key : INTERNAL_ERROR_KEY;
+ try {
+ return getReplyTextBundle().getString(msgKey);
+ }
+ catch (MissingResourceException e) {
+ // No reply text is mapped for the specified key
+ LOG.warn("No reply text defined for key [" + msgKey + "]");
+ return null;
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Login Support (used by USER and PASS commands)
+ // -------------------------------------------------------------------------
+
+ /**
+ * Validate the UserAccount for the specified username. If valid, return true. If the UserAccount does
+ * not exist or is invalid, log an error message, send back a reply code of 530 with an appropriate
+ * error message, and return false. A UserAccount is considered invalid if the homeDirectory property
+ * is not set or is set to a non-existent directory.
+ *
+ * @param username - the username
+ * @param session - the session; used to send back an error reply if necessary
+ * @return true only if the UserAccount for the named user is valid
+ */
+ protected boolean validateUserAccount(String username, Session session) {
+ UserAccount userAccount = serverConfiguration.getUserAccount(username);
+ if (userAccount == null || !userAccount.isValid()) {
+ LOG.error("UserAccount missing or not valid for username [" + username + "]: " + userAccount);
+ sendReply(session, ReplyCodes.USER_ACCOUNT_NOT_VALID, "login.userAccountNotValid", list(username));
+ return false;
+ }
+
+ String home = userAccount.getHomeDirectory();
+ if (!getFileSystem().isDirectory(home)) {
+ LOG.error("Home directory configured for username [" + username + "] is not valid: " + home);
+ sendReply(session, ReplyCodes.USER_ACCOUNT_NOT_VALID, "login.homeDirectoryNotValid", list(username, home));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Log in the specified user for the current session. Send back a reply of 230 with a message indicated
+ * by the replyMessageKey and set the UserAccount and current directory (homeDirectory) in the session.
+ *
+ * @param userAccount - the userAccount for the user to be logged in
+ * @param session - the session
+ * @param replyCode - the reply code to send
+ * @param replyMessageKey - the message key for the reply text
+ */
+ protected void login(UserAccount userAccount, Session session, int replyCode, String replyMessageKey) {
+ sendReply(session, replyCode, replyMessageKey);
+ session.setAttribute(SessionKeys.USER_ACCOUNT, userAccount);
+ session.setAttribute(SessionKeys.CURRENT_DIRECTORY, userAccount.getHomeDirectory());
+ }
+
+ /**
+ * Convenience method to return a List with the specified single item
+ *
+ * @param item - the single item in the returned List
+ * @return a new List with that single item
+ */
+ protected List list(Object item) {
+ return Collections.singletonList(item);
+ }
+
+ /**
+ * Convenience method to return a List with the specified two items
+ *
+ * @param item1 - the first item in the returned List
+ * @param item2 - the second item in the returned List
+ * @return a new List with the specified items
+ */
+ protected List list(Object item1, Object item2) {
+ List list = new ArrayList(2);
+ list.add(item1);
+ list.add(item2);
+ return list;
+ }
+
+ /**
+ * Return true if the specified string is null or empty
+ *
+ * @param string - the String to check; may be null
+ * @return true only if the specified String is null or empyt
+ */
+ protected boolean notNullOrEmpty(String string) {
+ return string != null && string.length() > 0;
+ }
+
+ /**
+ * Return the string unless it is null or empty, in which case return the defaultString.
+ *
+ * @param string - the String to check; may be null
+ * @param defaultString - the value to return if string is null or empty
+ * @return string if not null and not empty; otherwise return defaultString
+ */
+ protected String defaultIfNullOrEmpty(String string, String defaultString) {
+ return (notNullOrEmpty(string) ? string : defaultString);
+ }
+
+} \ No newline at end of file