chrismair | 00dc7bd | 2014-05-11 21:21:28 +0000 | [diff] [blame] | 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 | }
|