160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair/* 260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Copyright 2008 the original author or authors. 360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Licensed under the Apache License, Version 2.0 (the "License"); 560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * you may not use this file except in compliance with the License. 660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * You may obtain a copy of the License at 760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * http://www.apache.org/licenses/LICENSE-2.0 960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 1060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Unless required by applicable law or agreed to in writing, software 1160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * distributed under the License is distributed on an "AS IS" BASIS, 1260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * See the License for the specific language governing permissions and 1460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * limitations under the License. 1560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 1660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairpackage org.mockftpserver.fake.command; 1760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 1860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport org.mockftpserver.core.CommandSyntaxException; 1960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport org.mockftpserver.core.IllegalStateException; 2060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport org.mockftpserver.core.NotLoggedInException; 2160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport org.mockftpserver.core.command.AbstractCommandHandler; 2260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport org.mockftpserver.core.command.Command; 2360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport org.mockftpserver.core.command.ReplyCodes; 2460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport org.mockftpserver.core.session.Session; 2560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport org.mockftpserver.core.session.SessionKeys; 2660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport org.mockftpserver.core.util.Assert; 2760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport org.mockftpserver.fake.ServerConfiguration; 2860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport org.mockftpserver.fake.ServerConfigurationAware; 2960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport org.mockftpserver.fake.UserAccount; 3060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport org.mockftpserver.fake.filesystem.FileSystem; 3160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport org.mockftpserver.fake.filesystem.FileSystemEntry; 3260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport org.mockftpserver.fake.filesystem.FileSystemException; 3360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport org.mockftpserver.fake.filesystem.InvalidFilenameException; 3460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 3560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport java.text.MessageFormat; 3660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport java.util.ArrayList; 3760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport java.util.Collections; 3860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport java.util.List; 3960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairimport java.util.MissingResourceException; 4060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 4160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair/** 4260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Abstract superclass for CommandHandler classes for the "Fake" server. 4360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 4460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @author Chris Mair 4560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @version $Revision$ - $Date$ 4660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 4760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismairpublic abstract class AbstractFakeCommandHandler extends AbstractCommandHandler implements ServerConfigurationAware { 4860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 4960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected static final String INTERNAL_ERROR_KEY = "internalError"; 5060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 5160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair private ServerConfiguration serverConfiguration; 5260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 5360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 5460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Reply code sent back when a FileSystemException is caught by the {@link #handleCommand(Command, Session)} 5560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * This defaults to ReplyCodes.EXISTING_FILE_ERROR (550). 5660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 5760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected int replyCodeForFileSystemException = ReplyCodes.READ_FILE_ERROR; 5860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 5960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair public ServerConfiguration getServerConfiguration() { 6060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair return serverConfiguration; 6160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 6260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 6360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair public void setServerConfiguration(ServerConfiguration serverConfiguration) { 6460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair this.serverConfiguration = serverConfiguration; 6560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 6660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 6760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 6860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Use template method to centralize and ensure common validation 6960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 7060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair public void handleCommand(Command command, Session session) { 7160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair Assert.notNull(serverConfiguration, "serverConfiguration"); 7260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair Assert.notNull(command, "command"); 7360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair Assert.notNull(session, "session"); 7460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 7560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair try { 7660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair handle(command, session); 7760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 7860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair catch (CommandSyntaxException e) { 7960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair handleException(command, session, e, ReplyCodes.COMMAND_SYNTAX_ERROR); 8060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 8160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair catch (IllegalStateException e) { 8260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair handleException(command, session, e, ReplyCodes.ILLEGAL_STATE); 8360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 8460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair catch (NotLoggedInException e) { 8560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair handleException(command, session, e, ReplyCodes.NOT_LOGGED_IN); 8660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 8760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair catch (InvalidFilenameException e) { 8860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair handleFileSystemException(command, session, e, ReplyCodes.FILENAME_NOT_VALID, list(e.getPath())); 8960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 9060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair catch (FileSystemException e) { 9160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair handleFileSystemException(command, session, e, replyCodeForFileSystemException, list(e.getPath())); 9260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 9360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 9460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 9560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 9660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Convenience method to return the FileSystem stored in the ServerConfiguration 9760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 9860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @return the FileSystem 9960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 10060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected FileSystem getFileSystem() { 10160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair return serverConfiguration.getFileSystem(); 10260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 10360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 10460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 10560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Handle the specified command for the session. All checked exceptions are expected to be wrapped or handled 10660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * by the caller. 10760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 10860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param command - the Command to be handled 10960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param session - the session on which the Command was submitted 11060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 11160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected abstract void handle(Command command, Session session); 11260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 11360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair // ------------------------------------------------------------------------- 11460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair // Utility methods for subclasses 11560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair // ------------------------------------------------------------------------- 11660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 11760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 11860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Send a reply for this command on the control connection. 11960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * <p/> 12060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * The reply code is designated by the <code>replyCode</code> property, and the reply text 12160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * is retrieved from the <code>replyText</code> ResourceBundle, using the specified messageKey. 12260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 12360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param session - the Session 12460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param replyCode - the reply code 12560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param messageKey - the resource bundle key for the reply text 12660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @throws AssertionError - if session is null 12760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @see MessageFormat 12860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 12960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected void sendReply(Session session, int replyCode, String messageKey) { 13060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair sendReply(session, replyCode, messageKey, Collections.EMPTY_LIST); 13160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 13260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 13360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 13460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Send a reply for this command on the control connection. 13560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * <p/> 13660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * The reply code is designated by the <code>replyCode</code> property, and the reply text 13760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * is retrieved from the <code>replyText</code> ResourceBundle, using the specified messageKey. 13860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 13960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param session - the Session 14060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param replyCode - the reply code 14160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param messageKey - the resource bundle key for the reply text 14260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param args - the optional message arguments; defaults to [] 14360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @throws AssertionError - if session is null 14460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @see MessageFormat 14560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 14660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected void sendReply(Session session, int replyCode, String messageKey, List args) { 14760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair Assert.notNull(session, "session"); 14860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair assertValidReplyCode(replyCode); 14960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 15060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair String text = getTextForKey(messageKey); 15160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair String replyText = (args != null && !args.isEmpty()) ? MessageFormat.format(text, args.toArray()) : text; 15260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 15360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair String replyTextToLog = (replyText == null) ? "" : " " + replyText; 15460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair String argsToLog = (args != null && !args.isEmpty()) ? (" args=" + args) : ""; 15560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair LOG.info("Sending reply [" + replyCode + replyTextToLog + "]" + argsToLog); 15660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair session.sendReply(replyCode, replyText); 15760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 15860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 15960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 16060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Send a reply for this command on the control connection. 16160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * <p/> 16260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * The reply code is designated by the <code>replyCode</code> property, and the reply text 16360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * is retrieved from the <code>replyText</code> ResourceBundle, using the reply code as the key. 16460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 16560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param session - the Session 16660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param replyCode - the reply code 16760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @throws AssertionError - if session is null 16860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @see MessageFormat 16960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 17060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected void sendReply(Session session, int replyCode) { 17160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair sendReply(session, replyCode, Collections.EMPTY_LIST); 17260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 17360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 17460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 17560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Send a reply for this command on the control connection. 17660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * <p/> 17760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * The reply code is designated by the <code>replyCode</code> property, and the reply text 17860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * is retrieved from the <code>replyText</code> ResourceBundle, using the reply code as the key. 17960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 18060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param session - the Session 18160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param replyCode - the reply code 18260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param args - the optional message arguments; defaults to [] 18360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @throws AssertionError - if session is null 18460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @see MessageFormat 18560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 18660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected void sendReply(Session session, int replyCode, List args) { 18760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair sendReply(session, replyCode, Integer.toString(replyCode), args); 18860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 18960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 19060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 19160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Handle the exception caught during handleCommand() 19260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 19360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param command - the Command 19460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param session - the Session 19560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param exception - the caught exception 19660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param replyCode - the reply code that should be sent back 19760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 19860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair private void handleException(Command command, Session session, Throwable exception, int replyCode) { 19960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair LOG.warn("Error handling command: " + command + "; " + exception, exception); 20060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair sendReply(session, replyCode); 20160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 20260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 20360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 20460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Handle the exception caught during handleCommand() 20560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 20660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param command - the Command 20760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param session - the Session 20860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param exception - the caught exception 20960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param replyCode - the reply code that should be sent back 21060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param arg - the arg for the reply (message) 21160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 21260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair private void handleFileSystemException(Command command, Session session, FileSystemException exception, int replyCode, Object arg) { 21360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair LOG.warn("Error handling command: " + command + "; " + exception, exception); 21460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair sendReply(session, replyCode, exception.getMessageKey(), Collections.singletonList(arg)); 21560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 21660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 21760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 21860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Return the value of the named attribute within the session. 21960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 22060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param session - the Session 22160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param name - the name of the session attribute to retrieve 22260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @return the value of the named session attribute 22360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @throws IllegalStateException - if the Session does not contain the named attribute 22460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 22560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected Object getRequiredSessionAttribute(Session session, String name) { 22660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair Object value = session.getAttribute(name); 22760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair if (value == null) { 22860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair throw new IllegalStateException("Session missing required attribute [" + name + "]"); 22960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 23060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair return value; 23160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 23260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 23360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 23460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Verify that the current user (if any) has already logged in successfully. 23560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 23660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param session - the Session 23760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 23860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected void verifyLoggedIn(Session session) { 23960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair if (getUserAccount(session) == null) { 24060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair throw new NotLoggedInException("User has not logged in"); 24160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 24260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 24360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 24460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 24560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param session - the Session 24660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @return the UserAccount stored in the specified session; may be null 24760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 24860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected UserAccount getUserAccount(Session session) { 24960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair return (UserAccount) session.getAttribute(SessionKeys.USER_ACCOUNT); 25060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 25160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 25260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 25360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Verify that the specified condition related to the file system is true, 25460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * otherwise throw a FileSystemException. 25560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 25660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param condition - the condition that must be true 25760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param path - the path involved in the operation; this will be included in the 25860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * error message if the condition is not true. 25960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param messageKey - the message key for the exception message 26060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @throws FileSystemException - if the condition is not true 26160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 26260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected void verifyFileSystemCondition(boolean condition, String path, String messageKey) { 26360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair if (!condition) { 26460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair throw new FileSystemException(path, messageKey); 26560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 26660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 26760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 26860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 26960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Verify that the current user has execute permission to the specified path 27060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 27160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param session - the Session 27260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param path - the file system path 27360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @throws FileSystemException - if the condition is not true 27460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 27560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected void verifyExecutePermission(Session session, String path) { 27660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair UserAccount userAccount = getUserAccount(session); 27760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair FileSystemEntry entry = getFileSystem().getEntry(path); 27860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair verifyFileSystemCondition(userAccount.canExecute(entry), path, "filesystem.cannotExecute"); 27960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 28060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 28160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 28260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Verify that the current user has write permission to the specified path 28360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 28460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param session - the Session 28560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param path - the file system path 28660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @throws FileSystemException - if the condition is not true 28760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 28860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected void verifyWritePermission(Session session, String path) { 28960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair UserAccount userAccount = getUserAccount(session); 29060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair FileSystemEntry entry = getFileSystem().getEntry(path); 29160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair verifyFileSystemCondition(userAccount.canWrite(entry), path, "filesystem.cannotWrite"); 29260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 29360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 29460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 29560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Verify that the current user has read permission to the specified path 29660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 29760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param session - the Session 29860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param path - the file system path 29960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @throws FileSystemException - if the condition is not true 30060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 30160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected void verifyReadPermission(Session session, String path) { 30260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair UserAccount userAccount = getUserAccount(session); 30360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair FileSystemEntry entry = getFileSystem().getEntry(path); 30460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair verifyFileSystemCondition(userAccount.canRead(entry), path, "filesystem.cannotRead"); 30560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 30660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 30760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 30860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Return the full, absolute path for the specified abstract pathname. 30960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * If path is null, return the current directory (stored in the session). If 31060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * path represents an absolute path, then return path as is. Otherwise, path 31160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * is relative, so assemble the full path from the current directory 31260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * and the specified relative path. 31360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 31460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param session - the Session 31560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param path - the abstract pathname; may be null 31660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @return the resulting full, absolute path 31760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 31860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected String getRealPath(Session session, String path) { 31960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair String currentDirectory = (String) session.getAttribute(SessionKeys.CURRENT_DIRECTORY); 32060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair if (path == null) { 32160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair return currentDirectory; 32260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 32360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair if (getFileSystem().isAbsolute(path)) { 32460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair return path; 32560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 32660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair return getFileSystem().path(currentDirectory, path); 32760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 32860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 32960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 33060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Return the end-of-line character(s) used when building multi-line responses 33160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 33260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @return "\r\n" 33360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 33460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected String endOfLine() { 33560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair return "\r\n"; 33660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 33760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 33860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair private String getTextForKey(String key) { 33960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair String msgKey = (key != null) ? key : INTERNAL_ERROR_KEY; 34060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair try { 34160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair return getReplyTextBundle().getString(msgKey); 34260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 34360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair catch (MissingResourceException e) { 34460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair // No reply text is mapped for the specified key 34560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair LOG.warn("No reply text defined for key [" + msgKey + "]"); 34660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair return null; 34760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 34860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 34960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 35060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair // ------------------------------------------------------------------------- 35160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair // Login Support (used by USER and PASS commands) 35260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair // ------------------------------------------------------------------------- 35360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 35460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 35560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Validate the UserAccount for the specified username. If valid, return true. If the UserAccount does 35660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * not exist or is invalid, log an error message, send back a reply code of 530 with an appropriate 35760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * error message, and return false. A UserAccount is considered invalid if the homeDirectory property 35860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * is not set or is set to a non-existent directory. 35960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 36060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param username - the username 36160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param session - the session; used to send back an error reply if necessary 36260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @return true only if the UserAccount for the named user is valid 36360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 36460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected boolean validateUserAccount(String username, Session session) { 36560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair UserAccount userAccount = serverConfiguration.getUserAccount(username); 36660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair if (userAccount == null || !userAccount.isValid()) { 36760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair LOG.error("UserAccount missing or not valid for username [" + username + "]: " + userAccount); 36860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair sendReply(session, ReplyCodes.USER_ACCOUNT_NOT_VALID, "login.userAccountNotValid", list(username)); 36960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair return false; 37060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 37160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 37260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair String home = userAccount.getHomeDirectory(); 37360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair if (!getFileSystem().isDirectory(home)) { 37460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair LOG.error("Home directory configured for username [" + username + "] is not valid: " + home); 37560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair sendReply(session, ReplyCodes.USER_ACCOUNT_NOT_VALID, "login.homeDirectoryNotValid", list(username, home)); 37660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair return false; 37760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 37860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 37960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair return true; 38060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 38160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 38260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 38360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Log in the specified user for the current session. Send back a reply of 230 with a message indicated 38460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * by the replyMessageKey and set the UserAccount and current directory (homeDirectory) in the session. 38560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 38660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param userAccount - the userAccount for the user to be logged in 38760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param session - the session 38860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param replyCode - the reply code to send 38960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param replyMessageKey - the message key for the reply text 39060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 39160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected void login(UserAccount userAccount, Session session, int replyCode, String replyMessageKey) { 39260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair sendReply(session, replyCode, replyMessageKey); 39360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair session.setAttribute(SessionKeys.USER_ACCOUNT, userAccount); 39460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair session.setAttribute(SessionKeys.CURRENT_DIRECTORY, userAccount.getHomeDirectory()); 39560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 39660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 39760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 39860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Convenience method to return a List with the specified single item 39960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 40060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param item - the single item in the returned List 40160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @return a new List with that single item 40260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 40360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected List list(Object item) { 40460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair return Collections.singletonList(item); 40560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 40660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 40760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 40860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Convenience method to return a List with the specified two items 40960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 41060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param item1 - the first item in the returned List 41160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param item2 - the second item in the returned List 41260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @return a new List with the specified items 41360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 41460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected List list(Object item1, Object item2) { 41560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair List list = new ArrayList(2); 41660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair list.add(item1); 41760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair list.add(item2); 41860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair return list; 41960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 42060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 42160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 42260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Return true if the specified string is null or empty 42360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 42460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param string - the String to check; may be null 42560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @return true only if the specified String is null or empyt 42660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 42760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected boolean notNullOrEmpty(String string) { 42860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair return string != null && string.length() > 0; 42960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 43060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 43160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair /** 43260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * Return the string unless it is null or empty, in which case return the defaultString. 43360b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * 43460b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param string - the String to check; may be null 43560b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @param defaultString - the value to return if string is null or empty 43660b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair * @return string if not null and not empty; otherwise return defaultString 43760b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair */ 43860b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair protected String defaultIfNullOrEmpty(String string, String defaultString) { 43960b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair return (notNullOrEmpty(string) ? string : defaultString); 44060b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair } 44160b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair 44260b81e2faf8511148f0d1e8f296e0b40ce9c7971chrismair}