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