AbstractCommandHandler.java revision 79eb164945510f6fbb16bdd3b0c1fcdf0b7f72ef
1/* 2 * Copyright 2007 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.core.command; 17 18import org.apache.log4j.Logger; 19import org.mockftpserver.core.CommandSyntaxException; 20import org.mockftpserver.core.session.Session; 21import org.mockftpserver.core.util.Assert; 22import org.mockftpserver.core.util.AssertFailedException; 23 24import java.text.MessageFormat; 25import java.util.ArrayList; 26import java.util.List; 27import java.util.MissingResourceException; 28import java.util.ResourceBundle; 29 30/** 31 * The abstract superclass for CommandHandler classes. This class manages the List of 32 * InvocationRecord objects corresponding to each invocation of the command handler, 33 * and provides helper methods for subclasses. 34 * 35 * @author Chris Mair 36 * @version $Revision$ - $Date$ 37 */ 38public abstract class AbstractCommandHandler implements CommandHandler, ReplyTextBundleAware, InvocationHistory { 39 40 private static final Logger LOG = Logger.getLogger(AbstractCommandHandler.class); 41 42 private ResourceBundle replyTextBundle; 43 private List invocations = new ArrayList(); 44 45 // ------------------------------------------------------------------------- 46 // Template Method 47 // ------------------------------------------------------------------------- 48 49 /** 50 * Handle the specified command for the session. This method is declared to throw Exception, 51 * allowing CommandHandler implementations to avoid unnecessary exception-handling. All checked 52 * exceptions are expected to be wrapped and handled by the caller. 53 * 54 * @param command - the Command to be handled 55 * @param session - the session on which the Command was submitted 56 * @throws Exception 57 * @throws AssertFailedException - if the command or session is null 58 * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, 59 * org.mockftpserver.core.session.Session) 60 */ 61 public final void handleCommand(Command command, Session session) throws Exception { 62 Assert.notNull(command, "command"); 63 Assert.notNull(session, "session"); 64 InvocationRecord invocationRecord = new InvocationRecord(command, session.getClientHost()); 65 invocations.add(invocationRecord); 66 try { 67 handleCommand(command, session, invocationRecord); 68 } 69 catch (CommandSyntaxException e) { 70 sendReply(session, ReplyCodes.COMMAND_SYNTAX_ERROR, null, null, null); 71 } 72 invocationRecord.lock(); 73 } 74 75 /** 76 * Handle the specified command for the session. This method is declared to throw Exception, 77 * allowing CommandHandler implementations to avoid unnecessary exception-handling. All checked 78 * exceptions are expected to be wrapped and handled by the caller. 79 * 80 * @param command - the Command to be handled 81 * @param session - the session on which the Command was submitted 82 * @param invocationRecord - the InvocationRecord; CommandHandlers are expected to add 83 * handler-specific data to the InvocationRecord, as appropriate 84 * @throws Exception 85 */ 86 protected abstract void handleCommand(Command command, Session session, InvocationRecord invocationRecord) 87 throws Exception; 88 89 //------------------------------------------------------------------------- 90 // Support for reply text ResourceBundle 91 //------------------------------------------------------------------------- 92 93 /** 94 * Return the ResourceBundle containing the reply text messages 95 * 96 * @return the replyTextBundle 97 * @see org.mockftpserver.core.command.ReplyTextBundleAware#getReplyTextBundle() 98 */ 99 public ResourceBundle getReplyTextBundle() { 100 return replyTextBundle; 101 } 102 103 /** 104 * Set the ResourceBundle containing the reply text messages 105 * 106 * @param replyTextBundle - the replyTextBundle to set 107 * @see org.mockftpserver.core.command.ReplyTextBundleAware#setReplyTextBundle(java.util.ResourceBundle) 108 */ 109 public void setReplyTextBundle(ResourceBundle replyTextBundle) { 110 this.replyTextBundle = replyTextBundle; 111 } 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 determined by the following rules: 122 * <ol> 123 * <li>If the <code>replyText</code> property is non-null, then use that.</li> 124 * <li>Otherwise, if <code>replyMessageKey</code> is non-null, the use that to retrieve a 125 * localized message from the <code>replyText</code> ResourceBundle.</li> 126 * <li>Otherwise, retrieve the reply text from the <code>replyText</code> ResourceBundle, 127 * using the reply code as the key.</li> 128 * </ol> 129 * If the arguments Object[] is not null, then these arguments are substituted within the 130 * reply text using the {@link MessageFormat} class. 131 * 132 * @param session - the Session 133 * @param replyCode - the reply code 134 * @param replyMessageKey - if not null (and replyText is null), this is used as the ResourceBundle 135 * message key instead of the reply code. 136 * @param replyText - if non-null, this is used as the reply text 137 * @param arguments - the array of arguments to be formatted and substituted within the reply 138 * text; may be null 139 * @throws AssertFailedException - if session is null 140 * @see MessageFormat 141 */ 142 protected void sendReply(Session session, int replyCode, String replyMessageKey, String replyText, 143 Object[] arguments) { 144 145 Assert.notNull(session, "session"); 146 assertValidReplyCode(replyCode); 147 148 final Logger HANDLER_LOG = Logger.getLogger(getClass()); 149 String key = (replyMessageKey != null) ? replyMessageKey : Integer.toString(replyCode); 150 String text = getTextForReplyCode(replyCode, key, replyText, arguments); 151 String replyTextToLog = (text == null) ? "" : " " + text; 152 HANDLER_LOG.debug("Sending reply [" + replyCode + replyTextToLog + "]"); 153 session.sendReply(replyCode, text); 154 } 155 156 /** 157 * Return the specified text surrounded with double quotes 158 * 159 * @param text - the text to surround with quotes 160 * @return the text with leading and trailing double quotes 161 * @throws AssertFailedException - if text is null 162 */ 163 protected static String quotes(String text) { 164 Assert.notNull(text, "text"); 165 final String QUOTES = "\""; 166 return QUOTES + text + QUOTES; 167 } 168 169 /** 170 * Assert that the specified number is a valid reply code 171 * 172 * @param replyCode - the reply code to check 173 * @throws AssertFailedException - if the replyCode is invalid 174 */ 175 protected void assertValidReplyCode(int replyCode) { 176 Assert.isTrue(replyCode > 0, "The number [" + replyCode + "] is not a valid reply code"); 177 } 178 179 // ------------------------------------------------------------------------- 180 // InvocationHistory - Support for command history 181 // ------------------------------------------------------------------------- 182 183 /** 184 * @return the number of invocation records stored for this command handler instance 185 * @see org.mockftpserver.core.command.InvocationHistory#numberOfInvocations() 186 */ 187 public int numberOfInvocations() { 188 return invocations.size(); 189 } 190 191 /** 192 * Return the InvocationRecord representing the command invoction data for the nth invocation 193 * for this command handler instance. One InvocationRecord should be stored for each invocation 194 * of the CommandHandler. 195 * 196 * @param index - the index of the invocation record to return. The first record is at index zero. 197 * @return the InvocationRecord for the specified index 198 * @throws AssertFailedException - if there is no invocation record corresponding to the specified index 199 * @see org.mockftpserver.core.command.InvocationHistory#getInvocation(int) 200 */ 201 public InvocationRecord getInvocation(int index) { 202 return (InvocationRecord) invocations.get(index); 203 } 204 205 /** 206 * Clear out the invocation history for this CommandHandler. After invoking this method, the 207 * <code>numberOfInvocations()</code> method will return zero. 208 * 209 * @see org.mockftpserver.core.command.InvocationHistory#clearInvocations() 210 */ 211 public void clearInvocations() { 212 invocations.clear(); 213 } 214 215 // ------------------------------------------------------------------------- 216 // Internal Helper Methods 217 // ------------------------------------------------------------------------- 218 219 /** 220 * Return the text for the specified reply code, formatted using the message arguments, if 221 * supplied. If overrideText is not null, then return that. Otherwise, return the text mapped to 222 * the code from the replyText ResourceBundle. If the ResourceBundle contains no mapping, then 223 * return null. 224 * <p/> 225 * If arguments is not null, then the returned reply text if formatted using the 226 * {@link MessageFormat} class. 227 * 228 * @param code - the reply code 229 * @param messageKey - the key used to retrieve the reply text from the replyTextBundle 230 * @param overrideText - if not null, this is used instead of the text from the replyTextBundle. 231 * @param arguments - the array of arguments to be formatted and substituted within the reply 232 * text; may be null 233 * @return the text for the reply code; may be null 234 */ 235 private String getTextForReplyCode(int code, String messageKey, String overrideText, Object[] arguments) { 236 try { 237 String t = (overrideText == null) ? replyTextBundle.getString(messageKey) : overrideText; 238 String formattedMessage = MessageFormat.format(t, arguments); 239 return (formattedMessage == null) ? null : formattedMessage.trim(); 240 } 241 catch (MissingResourceException e) { 242 // No reply text is mapped for the specified key 243 LOG.warn("No reply text defined for reply code [" + code + "]"); 244 return null; 245 } 246 } 247 248} 249