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