com.apporiented.hermesftp.session.impl.FtpSessionContextImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.apporiented.hermesftp.session.impl.FtpSessionContextImpl.java

Source

/*
 * ------------------------------------------------------------------------------
 * Hermes FTP Server
 * Copyright (c) 2005-2014 Lars Behnke
 * ------------------------------------------------------------------------------
 * 
 * This file is part of Hermes FTP Server.
 * 
 * Hermes FTP Server is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * Hermes FTP Server is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Hermes FTP Server; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * ------------------------------------------------------------------------------
 */

package com.apporiented.hermesftp.session.impl;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;

import com.apporiented.hermesftp.cmd.SocketProvider;
import com.apporiented.hermesftp.common.FtpConstants;
import com.apporiented.hermesftp.common.FtpEventListener;
import com.apporiented.hermesftp.common.FtpServerOptions;
import com.apporiented.hermesftp.common.FtpSessionContext;
import com.apporiented.hermesftp.exception.FtpConfigException;
import com.apporiented.hermesftp.exception.FtpQuotaException;
import com.apporiented.hermesftp.usermanager.UserManager;
import com.apporiented.hermesftp.usermanager.model.GroupDataList;
import com.apporiented.hermesftp.usermanager.model.UserData;
import com.apporiented.hermesftp.utils.LoggingReader;
import com.apporiented.hermesftp.utils.LoggingWriter;
import com.apporiented.hermesftp.utils.VarMerger;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This class servers as a means of transportation for data shared by a single FTP session.
 * Instances of the <code>FtpSessionContextImpl</code> class are passed to each of the commands
 * while executing a FTP command sequence. The command objects read connection settings and other
 * options from the context. In turn data that may concern the general state of the FTP session can
 * be stored in the context.
 * 
 * @author Lars Behnke
 */
public class FtpSessionContextImpl implements FtpConstants, FtpSessionContext {

    private static Log log = LogFactory.getLog(FtpSessionContextImpl.class);

    private static int portIdx;

    private String user;

    private String password;

    private boolean authenticated;

    private int dataType = DT_BINARY;

    private int transmissionMode = MODE_STREAM;

    private int storageStructure = STRUCT_FILE;

    private String remoteDir;

    private Socket clientSocket;

    private BufferedReader clientCmdReader;

    private PrintWriter clientResponseWriter;

    private FtpServerOptions options;

    private FtpEventListener eventListener;

    private ResourceBundle resourceBundle;

    private SocketProvider dataSocketProvider;

    private UserManager userManager;

    private Date creationTime;

    private Map<String, Object> attributes;

    private Map<String, Long> sessionStatistics = Collections.synchronizedMap(new HashMap<String, Long>());

    /**
     * Constructor.
     * 
     * @param options The server options.
     * @param userManager The user manager.
     * @param resourceBundle The resource bundle that containts messages and texts.
     * @param listener The listener that is informed on session events.
     */
    public FtpSessionContextImpl(FtpServerOptions options, UserManager userManager, ResourceBundle resourceBundle,
            FtpEventListener listener) {
        super();
        this.userManager = userManager;
        this.resourceBundle = resourceBundle;
        this.options = options;
        this.attributes = Collections.synchronizedMap(new HashMap<String, Object>());
        this.eventListener = listener;
    }

    @Override
    public void check() throws FtpConfigException {
        try {
            String dirName = getOptions().getRootDir();
            File dir = new File(dirName);
            if (!dir.exists() || !dir.isDirectory()) {
                throw new FtpConfigException("Remote directory not found.");
            }
        } catch (RuntimeException e) {
            throw new FtpConfigException("Invalid configuration: " + e);
        }

    }

    /**
     * {@inheritDoc}
     */
    public Object getAttribute(String name) {
        return attributes.get(name);
    }

    /**
     * {@inheritDoc}
     */
    public void setAttribute(String name, Object value) {
        if (value == null) {
            attributes.remove(name);
        } else {
            attributes.put(name, value);
        }
    }

    /**
     * {@inheritDoc}
     */
    public FtpServerOptions getOptions() {
        return options;
    }

    /**
     * {@inheritDoc}
     */
    public String getOption(String key) {
        return getOptions().getProperty(key);
    }

    /**
     * {@inheritDoc}
     */
    public String getPassword() {
        return password;
    }

    /**
     * {@inheritDoc}
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * {@inheritDoc}
     */
    public String getRemoteDir() {
        if (remoteDir == null) {
            remoteDir = getOptions().getRootDir();
        }
        return remoteDir;
    }

    /**
     * {@inheritDoc}
     */
    public String getRemoteRelDir() {
        String relDir = null;
        try {
            String canDir = new File(getRemoteDir()).getCanonicalPath();
            canDir = FilenameUtils.normalizeNoEndSeparator(canDir);

            String canRoot = new File(getOptions().getRootDir()).getCanonicalPath();
            canRoot = FilenameUtils.normalizeNoEndSeparator(canRoot);

            if (canDir.toUpperCase().startsWith(canRoot.toUpperCase())) {
                relDir = canDir.substring(canRoot.length());
                if (!relDir.startsWith(File.separator)) {
                    relDir = File.separator + relDir;
                }
            } else {
                relDir = canDir;
            }

        } catch (IOException e) {
            log.error(e);
        }
        return relDir;
    }

    /**
     * {@inheritDoc}
     */
    public void setRemoteDir(String remoteDir) {
        this.remoteDir = FilenameUtils.normalizeNoEndSeparator(remoteDir);
    }

    /**
     * {@inheritDoc}
     */
    public String getUser() {
        return user;
    }

    /**
     * {@inheritDoc}
     */
    public void setUser(String user) {
        this.user = user;
    }

    /**
     * {@inheritDoc}
     */
    public FtpEventListener getEventListener() {
        return eventListener;
    }

    /**
     * {@inheritDoc}
     */
    public String getRes(String id) {
        return resourceBundle.getString(id);
    }

    /**
     * {@inheritDoc}
     */
    public boolean isAuthenticated() {
        return authenticated;
    }

    /**
     * {@inheritDoc}
     */
    public int getDataType() {
        return dataType;
    }

    /**
     * {@inheritDoc}
     */
    public void setDataType(int dataType) {
        this.dataType = dataType;
    }

    /**
     * {@inheritDoc}
     */
    public int getStorageStructure() {
        return storageStructure;
    }

    /**
     * {@inheritDoc}
     */
    public void setStorageStructure(int storageStructure) {
        this.storageStructure = storageStructure;
    }

    /**
     * {@inheritDoc}
     */
    public int getTransmissionMode() {
        return transmissionMode;
    }

    /**
     * {@inheritDoc}
     */
    public void setTransmissionMode(int transmissionMode) {
        this.transmissionMode = transmissionMode;
    }

    /**
     * {@inheritDoc}
     */
    public SocketProvider getDataSocketProvider() {
        return dataSocketProvider;
    }

    /**
     * {@inheritDoc}
     */
    public void setDataSocketProvider(SocketProvider provider) {
        this.dataSocketProvider = provider;
    }

    /**
     * {@inheritDoc}
     */
    public Socket getClientSocket() {
        return clientSocket;
    }

    /**
     * {@inheritDoc}
     */
    public void setClientSocket(Socket clientSocket) throws IOException {
        this.clientSocket = clientSocket;
        this.clientResponseWriter = new LoggingWriter(new OutputStreamWriter(clientSocket.getOutputStream()), true);
        this.clientCmdReader = new LoggingReader(new InputStreamReader(clientSocket.getInputStream()));
    }

    /**
     * {@inheritDoc}
     */
    public PrintWriter getClientResponseWriter() {
        return clientResponseWriter;
    }

    /**
     * {@inheritDoc}
     */
    public BufferedReader getClientCmdReader() {
        return clientCmdReader;
    }

    /**
     * {@inheritDoc}
     */
    public int getPermission(String path) {
        int result = PRIV_NONE;
        try {
            GroupDataList list = (GroupDataList) getAttribute(ATTR_GROUP_DATA);
            result = list.getPermission(path, getUser(), options.getRootDir());
        } catch (FtpConfigException e) {
            log.error(e);
        }
        return result;
    }

    /**
     * {@inheritDoc}
     */
    public UserManager getUserManager() {
        return userManager;
    }

    /**
     * {@inheritDoc}
     */
    public boolean authenticate() {
        authenticated = false;
        String dirName = null;
        try {
            authenticated = userManager.authenticate(getUser(), getPassword(), this);
            if (authenticated) {
                setAttribute(ATTR_LOGIN_TIME, new Date());
                UserData userData = userManager.getUserData(getUser());
                setAttribute(ATTR_USER_DATA, userData);
                GroupDataList groupList = userManager.getGroupDataList(getUser());
                setAttribute(ATTR_GROUP_DATA, groupList);
                dirName = getStartDir();
                File dir = new File(dirName);
                if (!dir.exists()) {
                    FileUtils.forceMkdir(dir);
                }
                setRemoteDir(dirName);
            }
        } catch (FtpConfigException e) {
            log.error(e);
        } catch (IOException e) {
            log.error("Could not create directory: " + dirName);
        }
        return authenticated;
    }

    private synchronized String getStartDir() throws FtpConfigException {
        UserData userData = (UserData) getAttribute(ATTR_USER_DATA);
        if (userData == null) {
            throw new FtpConfigException("User data not available");
        }
        VarMerger varMerger = new VarMerger(userData.getDir());
        Properties props = new Properties();
        props.setProperty("ftproot", FilenameUtils.separatorsToUnix(options.getRootDir()));
        props.setProperty("user", user);
        varMerger.merge(props);
        if (!varMerger.isReplacementComplete()) {
            throw new FtpConfigException("Unresolved placeholders in user configuration file found.");
        }
        return varMerger.getText();
    }

    /**
     * {@inheritDoc}
     */
    public UserData getUserData() {
        return (UserData) getAttribute(ATTR_USER_DATA);
    }

    /**
     * {@inheritDoc}
     */
    public void resetCredentials() {
        authenticated = false;
        setUser(null);
        setPassword(null);
    }

    /**
     * {@inheritDoc}
     */
    public void closeSockets() {
        if (getDataSocketProvider() != null) {
            getDataSocketProvider().closeSocket();
        }
    }

    /**
     * {@inheritDoc}
     */
    public String getCharset() {
        String charset;
        Boolean forceUtf8 = (Boolean) getAttribute(ATTR_FORCE_UTF8);
        if (forceUtf8 != null && forceUtf8) {
            charset = "UTF-8";
        } else {
            String key = getDataType() == DT_EBCDIC ? OPT_CHARSET_EBCDIC : OPT_CHARSET_ASCII;
            charset = getOptions().getProperty(key);
        }
        return charset;
    }

    /**
     * {@inheritDoc}
     */
    public Integer getNextPassivePort() {
        Integer port;
        Integer[] allowedPorts = getOptions().getAllowedPorts();
        if (allowedPorts == null || allowedPorts.length == 0) {

            /* Let the system decide which port to use. */
            port = 0;
        } else {

            /* Get the port from the user defined list. */
            port = allowedPorts[portIdx++];
            if (portIdx >= allowedPorts.length) {
                portIdx = 0;
            }
        }
        return port;

    }

    /**
     * {@inheritDoc}
     */
    public Date getCreationTime() {
        return creationTime;
    }

    /**
     * {@inheritDoc}
     */
    public void setCreationTime(Date creationTime) {
        this.creationTime = creationTime;
    }

    /**
     * {@inheritDoc}
     */
    public Map<String, Long> getSessionStatistics() {
        return sessionStatistics;
    }

    private int getUpperLimit(String globalOptionKey, String groupLimitKey) {
        long result;
        long globalLimit = getOptions().getInt(globalOptionKey, -1);

        GroupDataList list = (GroupDataList) getAttribute(ATTR_GROUP_DATA);
        long groupLimit = list.getUpperLimit(groupLimitKey);

        if (globalLimit < 0) {
            result = groupLimit;
        } else if (groupLimit < 0) {
            result = globalLimit;
        } else {
            result = Math.max(groupLimit, globalLimit);
        }

        return (int) result;
    }

    /**
     * {@inheritDoc}
     */
    public int getMaxDownloadRate() {
        return getUpperLimit(OPT_MAX_DOWNLOAD_RATE, STAT_DOWNLOAD_RATE);
    }

    /**
     * {@inheritDoc}
     */
    public int getMaxUploadRate() {
        return getUpperLimit(OPT_MAX_UPLOAD_RATE, STAT_UPLOAD_RATE);
    }

    /**
     * Increases a particular resource consumption by the passed value.
     * 
     * @param countKey The name of the statistic.
     * @param value The value
     * @throws FtpQuotaException Thrown if a resource limit has been reached.
     */
    public void updateIncrementalStat(String countKey, long value) throws FtpQuotaException {

        /* All sessions of user */
        getUserManager().updateIncrementalStatistics(getUser(), countKey, value);

        /* Current session */
        Map<String, Long> sessionStats = getSessionStatistics();
        Long consumptionObj = sessionStats.get(countKey);
        long consumption = consumptionObj == null ? 0 : consumptionObj;
        sessionStats.put(countKey, consumption + value);
    }

    /**
     * Updates the upload or download transfer rate taking the passed value into account.
     * 
     * @param avgKey The name of the statistic.
     * @param value The value
     */
    public void updateAverageStat(String avgKey, int value) {

        /* All sessions of user */
        getUserManager().updateAverageStatistics(getUser(), avgKey, value);

        /* Current session */
        String countKey = "Sample count (" + avgKey + ")";
        Map<String, Long> sessionStats = getSessionStatistics();
        Long prevAvgObj = sessionStats.get(avgKey);
        long prevAvg = prevAvgObj == null ? 0 : prevAvgObj;
        Long prevCountObj = sessionStats.get(countKey);
        long prevCount = prevCountObj == null ? 0 : prevCountObj;
        long currentAvg = (prevAvg * prevCount + value) / (prevCount + 1);
        sessionStats.put(avgKey, currentAvg);
        sessionStats.put(countKey, prevCount + 1);
    }

}