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 */ 16 package org.mockftpserver.core.command; 17 18 import org.mockftpserver.core.CommandSyntaxException; 19 import org.mockftpserver.core.session.Session; 20 import org.mockftpserver.core.util.Assert; 21 import org.mockftpserver.core.util.AssertFailedException; 22 23 import java.text.MessageFormat; 24 import java.util.ArrayList; 25 import java.util.List; 26 import java.util.MissingResourceException; 27 28 /** 29 * The abstract superclass for CommandHandler classes that manage the List of InvocationRecord 30 * objects corresponding to each invocation of the command handler, and provide helper methods for subclasses. 31 * 32 * @author Chris Mair 33 * @version $Revision$ - $Date$ 34 */ 35 public abstract class AbstractTrackingCommandHandler extends AbstractCommandHandler implements InvocationHistory { 36 37 private List invocations = new ArrayList(); 38 39 // ------------------------------------------------------------------------- 40 // Template Method 41 // ------------------------------------------------------------------------- 42 43 /** 44 * Handle the specified command for the session. This method is declared to throw Exception, 45 * allowing CommandHandler implementations to avoid unnecessary exception-handling. All checked 46 * exceptions are expected to be wrapped and handled by the caller. 47 * 48 * @param command - the Command to be handled 49 * @param session - the session on which the Command was submitted 50 * @throws Exception 51 * @throws AssertFailedException - if the command or session is null 52 * @see org.mockftpserver.core.command.CommandHandler#handleCommand(org.mockftpserver.core.command.Command, 53 * org.mockftpserver.core.session.Session) 54 */ 55 public final void handleCommand(Command command, Session session) throws Exception { 56 Assert.notNull(command, "command"); 57 Assert.notNull(session, "session"); 58 InvocationRecord invocationRecord = new InvocationRecord(command, session.getClientHost()); 59 invocations.add(invocationRecord); 60 try { 61 handleCommand(command, session, invocationRecord); 62 } 63 catch (CommandSyntaxException e) { 64 sendReply(session, ReplyCodes.COMMAND_SYNTAX_ERROR, null, null, null); 65 } 66 invocationRecord.lock(); 67 } 68 69 /** 70 * Handle the specified command for the session. This method is declared to throw Exception, 71 * allowing CommandHandler implementations to avoid unnecessary exception-handling. All checked 72 * exceptions are expected to be wrapped and handled by the caller. 73 * 74 * @param command - the Command to be handled 75 * @param session - the session on which the Command was submitted 76 * @param invocationRecord - the InvocationRecord; CommandHandlers are expected to add 77 * handler-specific data to the InvocationRecord, as appropriate 78 * @throws Exception 79 */ 80 protected abstract void handleCommand(Command command, Session session, InvocationRecord invocationRecord) 81 throws Exception; 82 83 // ------------------------------------------------------------------------- 84 // Utility methods for subclasses 85 // ------------------------------------------------------------------------- 86 87 /** 88 * Send a reply for this command on the control connection. 89 * <p/> 90 * The reply code is designated by the <code>replyCode</code> property, and the reply text 91 * is determined by the following rules: 92 * <ol> 93 * <li>If the <code>replyText</code> property is non-null, then use that.</li> 94 * <li>Otherwise, if <code>replyMessageKey</code> is non-null, the use that to retrieve a 95 * localized message from the <code>replyText</code> ResourceBundle.</li> 96 * <li>Otherwise, retrieve the reply text from the <code>replyText</code> ResourceBundle, 97 * using the reply code as the key.</li> 98 * </ol> 99 * If the arguments Object[] is not null, then these arguments are substituted within the 100 * reply text using the {@link MessageFormat} class. 101 * 102 * @param session - the Session 103 * @param replyCode - the reply code 104 * @param replyMessageKey - if not null (and replyText is null), this is used as the ResourceBundle 105 * message key instead of the reply code. 106 * @param replyText - if non-null, this is used as the reply text 107 * @param arguments - the array of arguments to be formatted and substituted within the reply 108 * text; may be null 109 * @throws AssertFailedException - if session is null 110 * @see MessageFormat 111 */ 112 protected void sendReply(Session session, int replyCode, String replyMessageKey, String replyText, 113 Object[] arguments) { 114 115 Assert.notNull(session, "session"); 116 assertValidReplyCode(replyCode); 117 118 String key = (replyMessageKey != null) ? replyMessageKey : Integer.toString(replyCode); 119 String text = getTextForReplyCode(replyCode, key, replyText, arguments); 120 String replyTextToLog = (text == null) ? "" : " " + text; 121 LOG.info("Sending reply [" + replyCode + replyTextToLog + "]"); 122 session.sendReply(replyCode, text); 123 } 124 125 // ------------------------------------------------------------------------- 126 // InvocationHistory - Support for command history 127 // ------------------------------------------------------------------------- 128 129 /** 130 * @return the number of invocation records stored for this command handler instance 131 * @see org.mockftpserver.core.command.InvocationHistory#numberOfInvocations() 132 */ 133 public int numberOfInvocations() { 134 return invocations.size(); 135 } 136 137 /** 138 * Return the InvocationRecord representing the command invoction data for the nth invocation 139 * for this command handler instance. One InvocationRecord should be stored for each invocation 140 * of the CommandHandler. 141 * 142 * @param index - the index of the invocation record to return. The first record is at index zero. 143 * @return the InvocationRecord for the specified index 144 * @throws AssertFailedException - if there is no invocation record corresponding to the specified index 145 * @see org.mockftpserver.core.command.InvocationHistory#getInvocation(int) 146 */ 147 public InvocationRecord getInvocation(int index) { 148 return (InvocationRecord) invocations.get(index); 149 } 150 151 /** 152 * Clear out the invocation history for this CommandHandler. After invoking this method, the 153 * <code>numberOfInvocations()</code> method will return zero. 154 * 155 * @see org.mockftpserver.core.command.InvocationHistory#clearInvocations() 156 */ 157 public void clearInvocations() { 158 invocations.clear(); 159 } 160 161 // ------------------------------------------------------------------------- 162 // Internal Helper Methods 163 // ------------------------------------------------------------------------- 164 165 /** 166 * Return the text for the specified reply code, formatted using the message arguments, if 167 * supplied. If overrideText is not null, then return that. Otherwise, return the text mapped to 168 * the code from the replyText ResourceBundle. If the ResourceBundle contains no mapping, then 169 * return null. 170 * <p/> 171 * If arguments is not null, then the returned reply text if formatted using the 172 * {@link MessageFormat} class. 173 * 174 * @param code - the reply code 175 * @param messageKey - the key used to retrieve the reply text from the replyTextBundle 176 * @param overrideText - if not null, this is used instead of the text from the replyTextBundle. 177 * @param arguments - the array of arguments to be formatted and substituted within the reply 178 * text; may be null 179 * @return the text for the reply code; may be null 180 */ 181 private String getTextForReplyCode(int code, String messageKey, String overrideText, Object[] arguments) { 182 try { 183 String t = (overrideText == null) ? getReplyTextBundle().getString(messageKey) : overrideText; 184 String formattedMessage = MessageFormat.format(t, arguments); 185 return (formattedMessage == null) ? null : formattedMessage.trim(); 186 } 187 catch (MissingResourceException e) { 188 // No reply text is mapped for the specified key 189 LOG.warn("No reply text defined for reply code [" + code + "]"); 190 return null; 191 } 192 } 193 194 } 195