All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mockftpserver.fake.command.AbstractFakeCommandHandler Maven / Gradle / Ivy

Go to download

The MockFtpServer project provides mock/dummy FTP server implementations for testing FTP client code. Two FTP Server implementations are provided, each at a different level of abstraction. FakeFtpServer provides a higher-level abstraction. You define a virtual file system, including directories and files, as well as a set of valid user accounts and credentials. The FakeFtpServer then responds with appropriate replies and reply codes based on that configuration. StubFtpServer, on the other hand, is a lower-level "stub" implementation. You configure the individual FTP server commands to return custom data or reply codes, allowing simulation of either success or failure scenarios. You can also verify expected command invocations.

There is a newer version: 3.2.0
Show newest version
/*
 * 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
 */
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.
     *
     * 

The reply code is designated by the replyCode property, and the reply text * is retrieved from the replyText 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. * *

The reply code is designated by the replyCode property, and the reply text * is retrieved from the replyText 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. * *

The reply code is designated by the replyCode property, and the reply text * is retrieved from the replyText 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. * *

The reply code is designated by the replyCode property, and the reply text * is retrieved from the replyText 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); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy