ApacheFTPServer的FtpServerFactory解析和应用(四)

1、FtpServerFactory

查看源代码,首先从FtpServerFactory开始,我们创建FTPServer第一步也是从它开始创建server,进行启动。

public class FtpServerFactory {

    private DefaultFtpServerContext serverContext;

    public FtpServerFactory() {
        serverContext = new DefaultFtpServerContext();
    }

    public FtpServer createServer() {
        return new DefaultFtpServer(serverContext);
    }

    public Map<String, Listener> getListeners() {
        return serverContext.getListeners();
    }

    public Listener getListener(final String name) {
        return serverContext.getListener(name);
    }

    public void addListener(final String name, final Listener listener) {
        serverContext.addListener(name, listener);
    }

    public void setListeners(final Map<String, Listener> listeners) {
        serverContext.setListeners(listeners);
    }
    
    public Map<String, Ftplet> getFtplets() {
        return serverContext.getFtpletContainer().getFtplets();
    }

    public void setFtplets(final Map<String, Ftplet> ftplets) {
        serverContext.setFtpletContainer(new DefaultFtpletContainer(ftplets));
    }

    public UserManager getUserManager() {
        return serverContext.getUserManager();
    }
    
    public void setUserManager(final UserManager userManager) {
        serverContext.setUserManager(userManager);
    }

    public FileSystemFactory getFileSystem() {
        return serverContext.getFileSystemManager();
    }
    
    public void setFileSystem(final FileSystemFactory fileSystem) {
        serverContext.setFileSystemManager(fileSystem);
    }

    public CommandFactory getCommandFactory() {
        return serverContext.getCommandFactory();
    }

    public void setCommandFactory(final CommandFactory commandFactory) {
        serverContext.setCommandFactory(commandFactory);
    }

    public MessageResource getMessageResource() {
        return serverContext.getMessageResource();
    }
    
    public void setMessageResource(final MessageResource messageResource) {
        serverContext.setMessageResource(messageResource);
    }

    public ConnectionConfig getConnectionConfig() {
        return serverContext.getConnectionConfig();
    }

    public void setConnectionConfig(final ConnectionConfig connectionConfig) {
        serverContext.setConnectionConfig(connectionConfig);
    }
}

它的属性只有一个DefaultFtpServerContext,其他的都是Get、Set方法,从DefaultFtpServerContext获取,主要有Listener(监听器,对外监听请求)、Ftplet(相当于JAVA的servlet,处理请求)、UserManager(用户管理组件,数据库用户就是这里配置)、FileSystemFactory(文件系统,主目录,切换目录,上传下载文件都是这个实现的)、CommandFactory(FTP命令的实现)、MessageResource(消息内容)、ConnectionConfig(连接配置)。默认这些参数都是DefaultFtpServerContext里配置的。我们再看看DefaultFtpServerContext

public class DefaultFtpServerContext implements FtpServerContext {

    private final Logger LOG = LoggerFactory
            .getLogger(DefaultFtpServerContext.class);

    private MessageResource messageResource = new MessageResourceFactory().createMessageResource();

    private UserManager userManager = new PropertiesUserManagerFactory().createUserManager();

    private FileSystemFactory fileSystemManager = new NativeFileSystemFactory();

    private FtpletContainer ftpletContainer = new DefaultFtpletContainer();

    private FtpStatistics statistics = new DefaultFtpStatistics();

    private CommandFactory commandFactory = new CommandFactoryFactory().createCommandFactory();

    private ConnectionConfig connectionConfig = new ConnectionConfigFactory().createConnectionConfig();

    private Map<String, Listener> listeners = new HashMap<String, Listener>();

    private static final List<Authority> ADMIN_AUTHORITIES = new ArrayList<Authority>();
    private static final List<Authority> ANON_AUTHORITIES = new ArrayList<Authority>();
    
    /**
     * The thread pool executor to be used by the server using this context
     */
    private ThreadPoolExecutor threadPoolExecutor = null;
    
    static {
        ADMIN_AUTHORITIES.add(new WritePermission());
        
        ANON_AUTHORITIES.add(new ConcurrentLoginPermission(20, 2));
        ANON_AUTHORITIES.add(new TransferRatePermission(4800, 4800));
    }
    

    public DefaultFtpServerContext() {
        // create the default listener
        listeners.put("default", new ListenerFactory().createListener());
    }
    
    public void createDefaultUsers() throws Exception {
        UserManager userManager = getUserManager();

        // create admin user
        String adminName = userManager.getAdminName();
        if (!userManager.doesExist(adminName)) {
            LOG.info("Creating user : " + adminName);
            BaseUser adminUser = new BaseUser();
            adminUser.setName(adminName);
            adminUser.setPassword(adminName);
            adminUser.setEnabled(true);

            adminUser.setAuthorities(ADMIN_AUTHORITIES);

            adminUser.setHomeDirectory("./res/home");
            adminUser.setMaxIdleTime(0);
            userManager.save(adminUser);
        }

        // create anonymous user
        if (!userManager.doesExist("anonymous")) {
            LOG.info("Creating user : anonymous");
            BaseUser anonUser = new BaseUser();
            anonUser.setName("anonymous");
            anonUser.setPassword("");

            anonUser.setAuthorities(ANON_AUTHORITIES);

            anonUser.setEnabled(true);

            anonUser.setHomeDirectory("./res/home");
            anonUser.setMaxIdleTime(300);
            userManager.save(anonUser);
        }
    }
    public UserManager getUserManager() {
        return userManager;
    }
    public FileSystemFactory getFileSystemManager() {
        return fileSystemManager;
    }
    public MessageResource getMessageResource() {
        return messageResource;
    }
    public FtpStatistics getFtpStatistics() {
        return statistics;
    }

    public void setFtpStatistics(FtpStatistics statistics) {
        this.statistics = statistics;
    }
    public FtpletContainer getFtpletContainer() {
        return ftpletContainer;
    }
    public CommandFactory getCommandFactory() {
        return commandFactory;
    }
    public Ftplet getFtplet(String name) {
        return ftpletContainer.getFtplet(name);
    }
    public void dispose() {
        listeners.clear();
        ftpletContainer.getFtplets().clear();
        if (threadPoolExecutor != null) {
            LOG.debug("Shutting down the thread pool executor");
            threadPoolExecutor.shutdown();
            try {
                threadPoolExecutor.awaitTermination(5000, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
            } finally {
                // TODO: how to handle?
            }
        }
    }

    public Listener getListener(String name) {
        return listeners.get(name);
    }

    public void setListener(String name, Listener listener) {
        listeners.put(name, listener);
    }

    public Map<String, Listener> getListeners() {
        return listeners;
    }

    public void setListeners(Map<String, Listener> listeners) {
        this.listeners = listeners;
    }

    public void addListener(String name, Listener listener) {
        listeners.put(name, listener);
    }

    public Listener removeListener(String name) {
        return listeners.remove(name);
    }

    public void setCommandFactory(CommandFactory commandFactory) {
        this.commandFactory = commandFactory;
    }

    public void setFileSystemManager(FileSystemFactory fileSystemManager) {
        this.fileSystemManager = fileSystemManager;
    }

    public void setFtpletContainer(FtpletContainer ftpletContainer) {
        this.ftpletContainer = ftpletContainer;
    }

    public void setMessageResource(MessageResource messageResource) {
        this.messageResource = messageResource;
    }

    public void setUserManager(UserManager userManager) {
        this.userManager = userManager;
    }

    public ConnectionConfig getConnectionConfig() {
        return connectionConfig;
    }

    public void setConnectionConfig(ConnectionConfig connectionConfig) {
        this.connectionConfig = connectionConfig;
    }
    
    public synchronized ThreadPoolExecutor getThreadPoolExecutor() {
        if(threadPoolExecutor == null) {
            int maxThreads = connectionConfig.getMaxThreads();
            if(maxThreads < 1) {
                int maxLogins = connectionConfig.getMaxLogins();
                if(maxLogins > 0) {
                    maxThreads = maxLogins;
                }
                else {
                    maxThreads = 16;
                }
            }
            LOG.debug("Intializing shared thread pool executor with max threads of {}", maxThreads);
            threadPoolExecutor = new OrderedThreadPoolExecutor(maxThreads);
        }
        return threadPoolExecutor;
    }
}

下面是MessageResource的初始化数据,是服务响应信息。

ApacheFTPServer的FtpServerFactory解析和应用(四)_第1张图片
实现我们自定义的需求就是修改DefaultFtpServerContext中的属性即可,不必重写整个DefaultFtpServerContext类,只替换到我们用到的,节省开发时间,提高系统稳定性。用数据库管理用户就在FtpServerFactory设置从DbUserManagerFactory获取的用户管理系统。用自己的FTPlet就在FtpServerFactory添加我们自己已实现Ftplet接口的类。用自己的监听器就向FtpServerFactory中添加我们自己的Listener,默认监听器在DefaultFtpServerContext是这样配置的,如下代码

private Map<String, Listener> listeners = new HashMap<String, Listener>();

    public DefaultFtpServerContext() {
        // create the default listener
        listeners.put("default", new ListenerFactory().createListener());
    }

2、CommandFactory命令实现

接下来讲最重要的命令系统和文件系统。上代码

private CommandFactory commandFactory = new CommandFactoryFactory().createCommandFactory();

public CommandFactory createCommandFactory() {
        
        Map<String, Command> mergedCommands = new HashMap<String, Command>();
        if(useDefaultCommands) {
            mergedCommands.putAll(DEFAULT_COMMAND_MAP);
        }
        
        mergedCommands.putAll(commandMap);
        
        return new DefaultCommandFactory(mergedCommands);
    }

我们可以看到CommandFactory里的默认命令都是以Map的数据结构维护的,如下代码:

private static final HashMap<String, Command> DEFAULT_COMMAND_MAP = new HashMap<String, Command>();

    static {
        // first populate the default command list
        DEFAULT_COMMAND_MAP.put("ABOR", new ABOR());
        DEFAULT_COMMAND_MAP.put("ACCT", new ACCT());
        DEFAULT_COMMAND_MAP.put("APPE", new APPE());
        DEFAULT_COMMAND_MAP.put("AUTH", new AUTH());
        DEFAULT_COMMAND_MAP.put("CDUP", new CDUP());
        DEFAULT_COMMAND_MAP.put("CWD", new CWD());
        DEFAULT_COMMAND_MAP.put("DELE", new DELE());
        DEFAULT_COMMAND_MAP.put("EPRT", new EPRT());
        DEFAULT_COMMAND_MAP.put("EPSV", new EPSV());
        DEFAULT_COMMAND_MAP.put("FEAT", new FEAT());
        DEFAULT_COMMAND_MAP.put("HELP", new HELP());
        DEFAULT_COMMAND_MAP.put("LANG", new LANG());
        DEFAULT_COMMAND_MAP.put("LIST", new LIST());
        DEFAULT_COMMAND_MAP.put("MD5", new MD5());
        DEFAULT_COMMAND_MAP.put("MFMT", new MFMT());
        DEFAULT_COMMAND_MAP.put("MMD5", new MD5());
        DEFAULT_COMMAND_MAP.put("MDTM", new MDTM());
        DEFAULT_COMMAND_MAP.put("MLST", new MLST());
        DEFAULT_COMMAND_MAP.put("MKD", new MKD());
        DEFAULT_COMMAND_MAP.put("MLSD", new MLSD());
        DEFAULT_COMMAND_MAP.put("MODE", new MODE());
        DEFAULT_COMMAND_MAP.put("NLST", new NLST());
        DEFAULT_COMMAND_MAP.put("NOOP", new NOOP());
        DEFAULT_COMMAND_MAP.put("OPTS", new OPTS());
        DEFAULT_COMMAND_MAP.put("PASS", new PASS());
        DEFAULT_COMMAND_MAP.put("PASV", new PASV());
        DEFAULT_COMMAND_MAP.put("PBSZ", new PBSZ());
        DEFAULT_COMMAND_MAP.put("PORT", new PORT());
        DEFAULT_COMMAND_MAP.put("PROT", new PROT());
        DEFAULT_COMMAND_MAP.put("PWD", new PWD());
        DEFAULT_COMMAND_MAP.put("QUIT", new QUIT());
        DEFAULT_COMMAND_MAP.put("REIN", new REIN());
        DEFAULT_COMMAND_MAP.put("REST", new REST());
        DEFAULT_COMMAND_MAP.put("RETR", new RETR());
        DEFAULT_COMMAND_MAP.put("RMD", new RMD());
        DEFAULT_COMMAND_MAP.put("RNFR", new RNFR());
        DEFAULT_COMMAND_MAP.put("RNTO", new RNTO());
        DEFAULT_COMMAND_MAP.put("SITE", new SITE());
        DEFAULT_COMMAND_MAP.put("SIZE", new SIZE());
        DEFAULT_COMMAND_MAP.put("SITE_DESCUSER", new SITE_DESCUSER());
        DEFAULT_COMMAND_MAP.put("SITE_HELP", new SITE_HELP());
        DEFAULT_COMMAND_MAP.put("SITE_STAT", new SITE_STAT());
        DEFAULT_COMMAND_MAP.put("SITE_WHO", new SITE_WHO());
        DEFAULT_COMMAND_MAP.put("SITE_ZONE", new SITE_ZONE());

        DEFAULT_COMMAND_MAP.put("STAT", new STAT());
        DEFAULT_COMMAND_MAP.put("STOR", new STOR());
        DEFAULT_COMMAND_MAP.put("STOU", new STOU());
        DEFAULT_COMMAND_MAP.put("STRU", new STRU());
        DEFAULT_COMMAND_MAP.put("SYST", new SYST());
        DEFAULT_COMMAND_MAP.put("TYPE", new TYPE());
        DEFAULT_COMMAND_MAP.put("USER", new USER());
    }

当我们要替换或增加自定义命令,利用Map即可实现,替换就put一个同名的命名key,放入自定义的命令实现类。(ps:我开发的有个自定义的文件系统需求,所以重写了PASS命名实现,在登录成功后,创建自己的文件管理系统支持服务)

2、FileSystemFactory文件管理系统

在DefaultFtpServerContext中可以看到默认的文件系统是本机文件系统,下代码:

private FileSystemFactory fileSystemManager = new NativeFileSystemFactory();

我们来梳理一下该文件系统的组织,首先用户登录成功后,在FtpServer中找到配置的文件管理系统工厂,默认为NativeFileSystemFactory,由它创建一个与用户相关联的
NativeFileSystemView。里面包含用户信息、根目录、当前目录,主要为用户提供切换目录,获取当前目录对象,获取当前目录下指定文件功能。该文件系统中实际使用的一个文件对象是NativeFtpFile,包含fileName、File、User,提供文件的所有操作。上代码:

public class NativeFileSystemFactory implements FileSystemFactory {
//创建FileSystemView对象
public FileSystemView createFileSystemView(User user) throws FtpException {
        synchronized (user) {
            // create home if does not exist
            if (createHome) {
                String homeDirStr = user.getHomeDirectory();
                File homeDir = new File(homeDirStr);
                if (homeDir.isFile()) {
                    LOG.warn("Not a directory :: " + homeDirStr);
                    throw new FtpException("Not a directory :: " + homeDirStr);
                }
                if ((!homeDir.exists()) && (!homeDir.mkdirs())) {
                    LOG.warn("Cannot create user home :: " + homeDirStr);
                    throw new FtpException("Cannot create user home :: "
                            + homeDirStr);
                }
            }

            FileSystemView fsView = new NativeFileSystemView(user,
                    caseInsensitive);
            return fsView;
        }
    }
}

public class NativeFileSystemView implements FileSystemView {
//获取根目录
public FtpFile getHomeDirectory() {
        return new NativeFtpFile("/", new File(rootDir), user);
    }

}
//获取当前目录对象
public FtpFile getWorkingDirectory() {
        FtpFile fileObj = null;
        if (currDir.equals("/")) {
            fileObj = new NativeFtpFile("/", new File(rootDir), user);
        } else {
            File file = new File(rootDir, currDir.substring(1));
            fileObj = new NativeFtpFile(currDir, file, user);

        }
        return fileObj;
}

//获取目录下文件
public FtpFile getFile(String file) {

        // get actual file object
        String physicalName = getPhysicalName(rootDir,
                currDir, file, caseInsensitive);
        File fileObj = new File(physicalName);

        // strip the root directory and return
        String userFileName = physicalName.substring(rootDir.length() - 1);
        return new NativeFtpFile(userFileName, fileObj, user);
}
//切换目录
public boolean changeWorkingDirectory(String dir) {

        // not a directory - return false
        dir = getPhysicalName(rootDir, currDir, dir,
                caseInsensitive);
        File dirObj = new File(dir);
        if (!dirObj.isDirectory()) {
            return false;
        }

        // strip user root and add last '/' if necessary
        dir = dir.substring(rootDir.length() - 1);
        if (dir.charAt(dir.length() - 1) != '/') {
            dir = dir + '/';
        }

        currDir = dir;
        return true;
}

NativeFtpFile可以当File一起看,基本实现都是File实现的。上述的三个文件系统必备的类,都各自实现了三个接口FileSystemFactory、FileSystemView、FtpFile,所以我们要创建自己的文件系统也要实现这三个接口,实现完成后,可以在FtpServerFactory设置我们自己的文件系统,如果要实现多文件系统相互切换,也可以在PASS命令里实现切换,不同登录的人,不同的端口号登录进入不同的文件系统。
到这里我们就对ApacheFTPServer有了初步的了解和应用,可以满足大部分FTP服务的需求,我们可以实现向web应用一样,接收FTP请求,可以转发,可以有自己的文件系统,用户管理系统,可以实现SSL加密连接数据传输,可以有自己的日志管理。现在我又接到一个BT需求,要实现Linux命名行连接FTPS,编写shell脚本实现自动文件传输任务。但是服务器自带的FTP又不支持,网上都是建议用户客户端,又和脚本冲突,难受!。

你可能感兴趣的:(ApacheFTPServer,JAVA,源码解析)