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 */ 16package org.mockftpserver.fake.command 17 18import org.mockftpserver.fake.ServerConfigurationAware 19import org.mockftpserver.fake.ServerConfiguration 20import org.mockftpserver.fake.filesystem.FileSystem 21import org.mockftpserver.fake.filesystem.ExistingFileOperationException 22import org.mockftpserver.fake.filesystem.NewFileOperationException 23import org.mockftpserver.core.CommandSyntaxException 24import org.mockftpserver.core.IllegalStateException 25import org.mockftpserver.core.NotLoggedInException 26import org.mockftpserver.core.command.Command 27import org.mockftpserver.core.command.CommandHandler 28import org.mockftpserver.core.command.ReplyCodes 29import org.mockftpserver.core.session.Session 30import org.mockftpserver.core.session.SessionKeys 31import org.apache.log4j.Logger 32import java.text.MessageFormat 33import org.mockftpserver.fake.filesystem.InvalidFilenameException 34 35/** 36 * Abstract superclass for CommandHandler classes for the "Fake" server. 37 * 38 * @version $Revision: $ - $Date: $ 39 * 40 * @author Chris Mair 41 */ 42abstract class AbstractFakeCommandHandler implements CommandHandler, ServerConfigurationAware { 43 44 final Logger LOG = Logger.getLogger(this.class) 45 ServerConfiguration serverConfiguration 46 47 /** 48 * Use template method to centralize and ensure common validation 49 */ 50 void handleCommand(Command command, Session session) { 51 assert serverConfiguration != null 52 assert command != null 53 assert session != null 54 55 try { 56 handle(command, session) 57 } 58 catch(CommandSyntaxException e) { 59 LOG.warn("Error handling command: $command; ${e}") 60 sendReply(session, ReplyCodes.COMMAND_SYNTAX_ERROR) 61 } 62 catch(IllegalStateException e) { 63 LOG.warn("Error handling command: $command; ${e}") 64 sendReply(session, ReplyCodes.ILLEGAL_STATE) 65 } 66 catch(NotLoggedInException e) { 67 LOG.warn("Error handling command: $command; ${e}") 68 sendReply(session, ReplyCodes.NOT_LOGGED_IN) 69 } 70 catch(ExistingFileOperationException e) { 71 LOG.warn("Error handling command: $command; ${e}; path: ${e.path}") 72 sendReply(session, ReplyCodes.EXISTING_FILE_ERROR, [e.path]) 73 } 74 catch(NewFileOperationException e) { 75 LOG.warn("Error handling command: $command; ${e}; path: ${e.path}") 76 sendReply(session, ReplyCodes.NEW_FILE_ERROR, [e.path]) 77 } 78 catch(InvalidFilenameException e) { 79 e.printStackTrace() 80 LOG.warn("Error handling command: $command; ${e}") 81 sendReply(session, ReplyCodes.FILENAME_NOT_VALID, [e.path]) 82 } 83 } 84 85 /** 86 * Convenience method to return the FileSystem stored in the ServerConfiguration 87 */ 88 protected FileSystem getFileSystem() { 89 serverConfiguration.fileSystem 90 } 91 92 /** 93 * Subclasses must implement this 94 */ 95 protected abstract void handle(Command command, Session session) 96 97 // ------------------------------------------------------------------------- 98 // Utility methods for subclasses 99 // ------------------------------------------------------------------------- 100 101 /** 102 * Send a reply for this command on the control connection. 103 * 104 * The reply code is designated by the <code>replyCode</code> property, and the reply text 105 * is retrieved from the <code>replyText</code> ResourceBundle, using the reply code as the key. 106 * 107 * @param session - the Session 108 * @param replyCode - the reply code 109 * @param args - the optional message arguments; defaults to [] 110 * 111 * @throws AssertionError - if session is null 112 * 113 * @see MessageFormat 114 */ 115 protected void sendReply(Session session, int replyCode, args = []) { 116 assert session 117 assertValidReplyCode(replyCode); 118 119 String key = Integer.toString(replyCode); 120 String text = serverConfiguration.getTextForReplyCode(replyCode) 121 122 String replyText = (args) ? MessageFormat.format(text, args as Object[]) : text; 123 124 String replyTextToLog = (replyText == null) ? "" : " " + replyText; 125 // TODO change to LOG.debug() 126 def argsToLog = (args) ? " args=$args" : "" 127 LOG.info("Sending reply [" + replyCode + replyTextToLog + "]" + argsToLog); 128 session.sendReply(replyCode, replyText); 129 } 130 131 /** 132 * Assert that the specified number is a valid reply code 133 * @param replyCode - the reply code to check 134 * 135 * @throws AssertionError - if the replyCode is invalid 136 */ 137 protected void assertValidReplyCode(int replyCode) { 138 assert replyCode > 0, "The number [" + replyCode + "] is not a valid reply code" 139 } 140 141 /** 142 * Return the value of the command's parameter at the specified index. 143 * @param command - the Command 144 * @param index - the index of the parameter to retrieve; defaults to zero 145 * @return the value of the command parameter 146 * @throws CommandSyntaxException if the Command does not have a parameter at that index 147 */ 148 protected String getRequiredParameter(Command command, int index=0) { 149 String value = command.getParameter(index) 150 if (!value) { 151 throw new CommandSyntaxException("$command missing required parameter at index [$index]") 152 } 153 return value 154 } 155 156 /** 157 * Return the value of the named attribute within the session. 158 * @param session - the Session 159 * @param name - the name of the session attribute to retrieve 160 * @return the value of the named session attribute 161 * @throws IllegalStateException - if the Session does not contain the named attribute 162 */ 163 protected Object getRequiredSessionAttribute(Session session, String name) { 164 Object value = session.getAttribute(name) 165 if (value == null) { 166 throw new IllegalStateException("Session missing required attribute [$name]") 167 } 168 return value 169 } 170 171 /** 172 * Verify that the current user (if any) has already logged in successfully. 173 * @param session - the Session 174 */ 175 protected void verifyLoggedIn(Session session) { 176 if (session.getAttribute(SessionKeys.USER_ACCOUNT) == null) { 177 throw new NotLoggedInException("User has not logged in") 178 } 179 } 180 181 /** 182 * Verify that the specified condition related to an existing file is true, 183 * otherwise throw a ExistingFileOperationException. 184 * 185 * @param condition - the condition that must be true 186 * @param path - the path involved in the operation; this will be included in the 187 * error message if the condition is not true. 188 * @throws ExistingFileOperationException - if the condition is not true 189 */ 190 protected void verifyForExistingFile(condition, path) { 191 if (!condition) { 192 throw new ExistingFileOperationException(path) 193 } 194 } 195 196 /** 197 * Verify that the specified condition related to a new file is true, 198 * otherwise throw a NewFileOperationException. 199 * 200 * @param condition - the condition that must be true 201 * @param path - the path involved in the operation; this will be included in the 202 * error message if the condition is not true. 203 * @throws NewFileOperationException - if the condition is not true 204 */ 205 protected void verifyForNewFile(condition, path) { 206 if (!condition) { 207 throw new NewFileOperationException(path) 208 } 209 } 210 211 /** 212 * Return the full, absolute path for the specified abstract pathname. 213 * If path is null, return the current directory (stored in the session). If 214 * path represents an absolute path, then return path as is. Otherwise, path 215 * is relative, so assemble the full path from the current directory 216 * and the specified relative path. 217 * @param Session - the Session 218 * @param path - the abstract pathname; may be null 219 * @return the resulting full, absolute path 220 */ 221 protected String getRealPath(Session session, String path) { 222 def currentDirectory = session.getAttribute(SessionKeys.CURRENT_DIRECTORY) 223 if (path == null) { 224 return currentDirectory 225 } 226 if (fileSystem.isAbsolute(path)) { 227 return path 228 } 229 return fileSystem.path(currentDirectory, path) 230 } 231 232 /** 233 * Return the end-of-line character(s) used when building multi-line responses 234 */ 235 protected String endOfLine() { 236 "\n" 237 } 238}