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}