incubator-sling-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From fmesc...@apache.org
Subject svn commit: r1436258 - in /sling/whiteboard/fmeschbe/ftpserver: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/sling/ src/main/java/org/apache/sling/ftpserver/ src/main/java/org/apache/sling/ftpse...
Date Mon, 21 Jan 2013 10:22:27 GMT
Author: fmeschbe
Date: Mon Jan 21 10:22:27 2013
New Revision: 1436258

URL: http://svn.apache.org/viewvc?rev=1436258&view=rev
Log:
Add Apache Mina FTP Server based Sling FTP Server

Added:
    sling/whiteboard/fmeschbe/ftpserver/   (with props)
    sling/whiteboard/fmeschbe/ftpserver/pom.xml
    sling/whiteboard/fmeschbe/ftpserver/src/
    sling/whiteboard/fmeschbe/ftpserver/src/main/
    sling/whiteboard/fmeschbe/ftpserver/src/main/java/
    sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/
    sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/
    sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/
    sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/
    sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/
    sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingConfiguration.java
    sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFileSystemFactory.java
    sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFileSystemView.java
    sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpFile.java
    sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpOutputStream.java
    sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpServer.java
    sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpletProxy.java
    sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingUser.java
    sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingUserManager.java
    sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingWritePermission.java
    sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/WebConsoleFtpLet.java

Propchange: sling/whiteboard/fmeschbe/ftpserver/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Mon Jan 21 10:22:27 2013
@@ -0,0 +1,4 @@
+.classpath
+.project
+.settings
+target

Added: sling/whiteboard/fmeschbe/ftpserver/pom.xml
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/ftpserver/pom.xml?rev=1436258&view=auto
==============================================================================
--- sling/whiteboard/fmeschbe/ftpserver/pom.xml (added)
+++ sling/whiteboard/fmeschbe/ftpserver/pom.xml Mon Jan 21 10:22:27 2013
@@ -0,0 +1,174 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+    
+    http://www.apache.org/licenses/LICENSE-2.0
+    
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>11</version>
+    </parent>
+
+    <artifactId>org.apache.sling.ftpserver</artifactId>
+    <packaging>bundle</packaging>
+    <version>0.0.1-SNAPSHOT</version>
+
+    <name>Apache Sling FTP Server</name>
+    <description>
+        Exposes the repository as an FTP Server based on
+        the Apache Mina FTP Server
+    </description>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/ftpserver</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/ftpserver</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/ftpserver</url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Import-Package>
+                            !org.springframework.*,
+                            *
+                        </Import-Package>
+                        <Embed-Dependency>
+                            ftpserver-core,mina-core
+                        </Embed-Dependency>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <version>2.3.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.osgi</artifactId>
+            <version>2.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Apache Mina FTP Server -->
+        <dependency>
+            <groupId>org.apache.mina</groupId>
+            <artifactId>mina-core</artifactId>
+            <version>2.0.4</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.ftpserver</groupId>
+            <artifactId>ftpserver-core</artifactId>
+            <version>1.0.6</version>
+            <scope>provided</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.ftpserver</groupId>
+                    <artifactId>ftplet-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- User Management -->
+        <dependency>
+            <groupId>javax.jcr</groupId>
+            <artifactId>jcr</artifactId>
+            <version>2.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-api</artifactId>
+            <version>2.4.0</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+        </dependency>
+        
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+        </dependency>
+
+        <!-- Testing -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.testing</artifactId>
+            <version>2.0.6</version>
+            <scope>test</scope>
+            <exclusions>
+                <!-- slf4j simple implementation logs INFO + higher to stdout (we don't want that behaviour) -->
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-simple</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <!-- using log4j under slf4j to allow fine-grained logging config (see src/test/resources/log4j.properties) -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <version>1.5.0</version>            
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <version>1.2.13</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>

Added: sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingConfiguration.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingConfiguration.java?rev=1436258&view=auto
==============================================================================
--- sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingConfiguration.java (added)
+++ sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingConfiguration.java Mon Jan 21 10:22:27 2013
@@ -0,0 +1,152 @@
+/*************************************************************************
+ *
+ * ADOBE CONFIDENTIAL
+ * ___________________
+ *
+ *  Copyright 2013 Adobe Systems Incorporated
+ *  All Rights Reserved.
+ *
+ * NOTICE:  All information contained herein is, and remains
+ * the property of Adobe Systems Incorporated and its suppliers,
+ * if any.  The intellectual and technical concepts contained
+ * herein are proprietary to Adobe Systems Incorporated and its
+ * suppliers and are protected by trade secret or copyright law.
+ * Dissemination of this information or reproduction of this material
+ * is strictly forbidden unless prior written permission is obtained
+ * from Adobe Systems Incorporated.
+ **************************************************************************/
+package org.apache.sling.ftpserver.impl;
+
+import java.util.Map;
+
+import org.apache.ftpserver.ConnectionConfig;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+
+public class SlingConfiguration implements ConnectionConfig {
+
+    private final Map<String, Object> config;
+
+    static final String NAME = "org.apache.sling.ftpserver.SlingFtpServer";
+
+    static final String PROP_PORT = "port";
+
+    static final int PROP_PORT_DEFAULT = 1221;
+
+    static final String PROP_MAX_LOGINS = "maxLogins";
+
+    static final int PROP_MAX_LOGINS_DEFAULT = 10;
+
+    static final String PROP_ANONYMOUS_LOGINS = "anonymousLoginEnabled";
+
+    static final boolean PROP_ANONYMOUS_LOGINS_DEFAULT = true;
+
+    static final String PROP_MAX_ANONYMOUS_LOGINS = "maxAnonymousLogins";
+
+    static final int PROP_MAX_ANONYMOUS_LOGINS_DEFAULT = 10;
+
+    static final String PROP_MAX_LOGIN_FAILURES = "maxLoginFailures";
+
+    static final int PROP_MAX_LOGIN_FAILURES_DEFAULT = 3;
+
+    static final String PROP_LOGIN_FAILURE_DELAY = "loginFailureDelay";
+
+    static final int PROP_LOGIN_FAILURE_DELAY_DEFAUT = 500;
+
+    static final String PROP_MAX_THREADS = "maxThreads";
+
+    static final int PROP_MAX_THREADS_DEFAULT = 0;
+
+    static final String PROP_ENABLED = "enabled";
+
+    static final boolean PROP_ENABLED_DEFAULT = true;
+
+    static final String PROP_MAX_IDLE_TIME = "max.idle.time.sec";
+
+    static final int PROP_MAX_IDLE_TIME_DEFAULT = 10 * 60;
+
+    static final String PROP_MAX_CONCURRENT = "max.concurrent";
+
+    static final int PROP_MAX_CONCURRENT_DEFAULT = 10;
+
+    static final String PROP_MAX_CONCURRENT_IP = "max.concurrent.ip";
+
+    static final int PROP_MAX_CONCURRENT_IP_DEFAULT = 10;
+
+    static final String PROP_MAX_UPLOAD = "max.upload";
+
+    static final int PROP_MAX_UPLOAD_DEFAULT = 0;
+
+    static final String PROP_MAX_DOWNLOAD = "max.download";
+
+    static final int PROP_MAX_DOWNLOAD_DEFAULT = 0;
+
+    public SlingConfiguration(final Map<String, Object> config) {
+        this.config = config;
+    }
+
+    public int getMaxLoginFailures() {
+        return get(PROP_MAX_LOGIN_FAILURES, PROP_MAX_LOGIN_FAILURES_DEFAULT);
+    }
+
+    public int getLoginFailureDelay() {
+        return get(PROP_LOGIN_FAILURE_DELAY, PROP_LOGIN_FAILURE_DELAY_DEFAUT);
+    }
+
+    public int getMaxAnonymousLogins() {
+        return get(PROP_MAX_ANONYMOUS_LOGINS, PROP_MAX_ANONYMOUS_LOGINS_DEFAULT);
+    }
+
+    public int getMaxLogins() {
+        return get(PROP_MAX_LOGINS, PROP_MAX_LOGINS_DEFAULT);
+    }
+
+    public boolean isAnonymousLoginEnabled() {
+        return get(PROP_ANONYMOUS_LOGINS, PROP_ANONYMOUS_LOGINS_DEFAULT);
+    }
+
+    public int getMaxThreads() {
+        return get(PROP_MAX_THREADS, PROP_MAX_THREADS_DEFAULT);
+    }
+
+    int getPort() {
+        return get(PROP_PORT, PROP_PORT_DEFAULT);
+    }
+
+    // whether users are enabled by default
+    boolean isEnabled() {
+        return get(PROP_ENABLED, PROP_ENABLED_DEFAULT);
+    }
+
+    // max idle time in seconds for users
+    int getMaxIdelTimeSec() {
+        return get(PROP_MAX_IDLE_TIME, PROP_MAX_IDLE_TIME_DEFAULT);
+    }
+
+    // max concurrent logins per user
+    int getMaxConcurrent() {
+        return get(PROP_MAX_CONCURRENT, PROP_MAX_CONCURRENT_DEFAULT);
+    }
+
+    // max concurrent logins per user from same IP
+    int getMaxConcurrentPerIp() {
+        return get(PROP_MAX_CONCURRENT_IP, PROP_MAX_CONCURRENT_IP_DEFAULT);
+    }
+
+    // max rate of data downloads
+    int getMaxDownloadRate() {
+        return get(PROP_MAX_DOWNLOAD, PROP_MAX_DOWNLOAD_DEFAULT);
+    }
+
+    // max rate of data uploads
+    int getMaxUploadRate() {
+        return get(PROP_MAX_UPLOAD, PROP_MAX_UPLOAD_DEFAULT);
+    }
+
+    boolean get(final String name, final boolean defaultValue) {
+        return PropertiesUtil.toBoolean(this.config.get(name), defaultValue);
+    }
+
+    int get(final String name, final int defaultValue) {
+        return PropertiesUtil.toInteger(this.config.get(name), defaultValue);
+    }
+}

Added: sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFileSystemFactory.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFileSystemFactory.java?rev=1436258&view=auto
==============================================================================
--- sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFileSystemFactory.java (added)
+++ sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFileSystemFactory.java Mon Jan 21 10:22:27 2013
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.ftpserver.impl;
+
+import org.apache.ftpserver.ftplet.FileSystemFactory;
+import org.apache.ftpserver.ftplet.FileSystemView;
+import org.apache.ftpserver.ftplet.FtpException;
+import org.apache.ftpserver.ftplet.User;
+
+public class SlingFileSystemFactory implements FileSystemFactory {
+
+    public FileSystemView createFileSystemView(User user) throws FtpException {
+        if (user instanceof SlingUser) {
+            return new SlingFileSystemView(((SlingUser) user).getResolver());
+        }
+
+        throw new FtpException("User " + user.getName() + " of type " + user.getClass() + " not supported");
+    }
+
+}

Added: sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFileSystemView.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFileSystemView.java?rev=1436258&view=auto
==============================================================================
--- sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFileSystemView.java (added)
+++ sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFileSystemView.java Mon Jan 21 10:22:27 2013
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.ftpserver.impl;
+
+import org.apache.ftpserver.ftplet.FileSystemView;
+import org.apache.ftpserver.ftplet.FtpFile;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+
+public class SlingFileSystemView implements FileSystemView {
+
+    private final ResourceResolver resolver;
+
+    private FtpFile cwd;
+
+    public SlingFileSystemView(final ResourceResolver resolver) {
+        this.resolver = resolver;
+        this.cwd = getHomeDirectory();
+    }
+
+    public boolean changeWorkingDirectory(String wd) {
+        final String cwd;
+        if (wd.startsWith("/")) {
+            cwd = wd;
+        } else {
+            cwd = this.cwd.getAbsolutePath() + "/" + wd;
+        }
+
+        FtpFile cwdFile = getFile(cwd);
+        if (cwdFile.doesExist()) {
+            this.cwd = cwdFile;
+            return true;
+        }
+
+        return false;
+    }
+
+    public void dispose() {
+        this.resolver.close();
+    }
+
+    public FtpFile getFile(String path) {
+        if (!path.startsWith("/")) {
+            path = this.cwd.getAbsolutePath() + "/" + path;
+        }
+
+        path = ResourceUtil.normalize(path);
+        Resource res = this.resolver.getResource(path);
+        if (res != null) {
+            return new SlingFtpFile(res);
+        }
+
+        return new SlingFtpFile(path, this.resolver);
+    }
+
+    public FtpFile getHomeDirectory() {
+        return getFile("/");
+    }
+
+    public FtpFile getWorkingDirectory() {
+        return this.cwd;
+    }
+
+    public boolean isRandomAccessible() {
+        // only stream access to data
+        return false;
+    }
+
+}

Added: sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpFile.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpFile.java?rev=1436258&view=auto
==============================================================================
--- sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpFile.java (added)
+++ sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpFile.java Mon Jan 21 10:22:27 2013
@@ -0,0 +1,276 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.ftpserver.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.ftpserver.ftplet.FtpFile;
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.slf4j.LoggerFactory;
+
+public class SlingFtpFile implements FtpFile {
+
+    private final ResourceResolver resolver;
+
+    private Resource resource;
+
+    private final String absPath;
+
+    private final String name;
+
+    public SlingFtpFile(final Resource resource) {
+        this.resolver = resource.getResourceResolver();
+        this.resource = resource;
+        this.absPath = this.resource.getPath();
+        this.name = this.resource.getName();
+    }
+
+    public SlingFtpFile(final String absPath, final ResourceResolver resolver) {
+        this.resolver = resolver;
+        this.resource = null;
+        this.absPath = absPath;
+        this.name = ResourceUtil.getName(absPath);
+    }
+
+    public InputStream createInputStream(long offset) throws IOException {
+        if (offset != 0) {
+            throw new IOException("random access not supported");
+        }
+        return this.resource.adaptTo(InputStream.class);
+    }
+
+    public OutputStream createOutputStream(long offset) throws IOException {
+        if (offset != 0) {
+            throw new IOException("random access not supported");
+        }
+
+        Resource content = getContent();
+        if (content != null) {
+            return new SlingFtpOutputStream(content);
+        }
+
+        throw new IOException("Cannot create OutputStream to " + this.getAbsolutePath());
+    }
+
+    public boolean delete() {
+        if (this.resource != null) {
+            try {
+                this.resolver.delete(this.resource);
+                this.resolver.commit();
+                return true;
+            } catch (PersistenceException pe) {
+                LoggerFactory.getLogger(getClass()).error("delete: Failed removing", pe);
+            }
+        }
+
+        return false;
+    }
+
+    public boolean doesExist() {
+        return this.resource != null;
+    }
+
+    public String getAbsolutePath() {
+        return this.absPath;
+    }
+
+    public String getGroupName() {
+        // no owner groups
+        return "nobody";
+    }
+
+    public long getLastModified() {
+        long time = this.resource.getResourceMetadata().getModificationTime();
+        if (time < 0) {
+            time = this.resource.getResourceMetadata().getCreationTime();
+            if (time < 0) {
+                time = System.currentTimeMillis();
+            }
+        }
+        return time;
+    }
+
+    public int getLinkCount() {
+        // number of children from listFiles()
+        return listFiles().size();
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getOwnerName() {
+        // no owner name
+        return "nobody";
+    }
+
+    public long getSize() {
+        return this.resource.getResourceMetadata().getContentLength();
+    }
+
+    public boolean isDirectory() {
+        try {
+            InputStream ins = this.createInputStream(0);
+            if (ins == null) {
+                return true;
+            }
+
+            ins.close();
+        } catch (IOException ignore) {
+        }
+
+        return false;
+    }
+
+    public boolean isFile() {
+        return !isDirectory();
+    }
+
+    public boolean isHidden() {
+        // we don't hide what is not hidden by ACL
+        return false;
+    }
+
+    public boolean isReadable() {
+        return true;
+    }
+
+    public boolean isRemovable() {
+        // TODO Consider access control !!
+        return true;
+    }
+
+    public boolean isWritable() {
+        // TODO Consider access control !!
+        return true;
+    }
+
+    public List<FtpFile> listFiles() {
+        Iterator<Resource> children = this.resource.listChildren();
+        ArrayList<FtpFile> list = new ArrayList<FtpFile>();
+        while (children.hasNext()) {
+            Resource child = children.next();
+            if (!"jcr:content".equals(child.getName())) {
+                list.add(new SlingFtpFile(child));
+            }
+        }
+        return list;
+    }
+
+    public boolean mkdir() {
+        if (!doesExist()) {
+            try {
+                this.resource = create(this.absPath, "sling:Folder");
+                this.resolver.commit();
+                return this.resource != null;
+            } catch (PersistenceException e) {
+                // TODO Auto-generated catch block
+            }
+        }
+
+        // already exists or error creating
+        return false;
+    }
+
+    public boolean move(FtpFile arg0) {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    public boolean setLastModified(long arg0) {
+        Resource content = getContent();
+        if (content != null) {
+            ModifiableValueMap map = content.adaptTo(ModifiableValueMap.class);
+            if (map != null) {
+                map.put("jcr:lastModified", arg0);
+                try {
+                    this.resolver.commit();
+                    return true;
+                } catch (PersistenceException e) {
+                    // TODO: handle
+                }
+            }
+        }
+
+        // error fallback
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return this.getAbsolutePath();
+    }
+
+    @SuppressWarnings("serial")
+    private Resource create(final String path, final String type) throws PersistenceException {
+        Resource parent = this.resolver.getResource(ResourceUtil.getParent(path));
+        if (parent != null) {
+            return this.resolver.create(parent, this.getName(), new HashMap<String, Object>() {
+                {
+                    put("jcr:primaryType", type);
+                }
+            });
+        }
+
+        return null;
+    }
+
+    @SuppressWarnings("serial")
+    private Resource createFile(final String path) throws PersistenceException {
+        Resource file = this.create(path, "nt:file");
+        if (file != null) {
+            return this.resolver.create(file, "jcr:content", new HashMap<String, Object>() {
+                {
+                    put("jcr:primaryType", "nt:unstructured");
+                }
+            });
+        }
+
+        return null;
+    }
+
+    private Resource getContent() {
+        if (this.resource == null) {
+            // create the resource ??
+            try {
+                return createFile(this.absPath);
+            } catch (PersistenceException e) {
+                // TODO Auto-generated catch block
+            }
+        } else {
+            Resource content = this.resource.getChild("jcr:content");
+            if (content == null) {
+                content = this.resource;
+            }
+            return content;
+        }
+
+        return null;
+    }
+}

Added: sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpOutputStream.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpOutputStream.java?rev=1436258&view=auto
==============================================================================
--- sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpOutputStream.java (added)
+++ sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpOutputStream.java Mon Jan 21 10:22:27 2013
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.ftpserver.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.Resource;
+
+public class SlingFtpOutputStream extends FilterOutputStream {
+
+    private final Resource content;
+
+    private final File tmpFile;
+
+    public SlingFtpOutputStream(final Resource content) throws IOException {
+        super(null);
+
+        this.content = content;
+        this.tmpFile = File.createTempFile("slingftp", ".uploadtmp");
+        this.out = new FileOutputStream(this.tmpFile);
+    }
+
+    @Override
+    public void close() throws IOException {
+        InputStream ins = null;
+        try {
+            super.close();
+
+            ModifiableValueMap map = this.content.adaptTo(ModifiableValueMap.class);
+            if (map != null) {
+                ins = new FileInputStream(tmpFile);
+                map.put("jcr:lastModified", System.currentTimeMillis());
+                map.put("jcr:data", ins);
+                this.content.getResourceResolver().commit();
+            }
+        } finally {
+            if (ins != null) {
+                try {
+                    ins.close();
+                } catch (IOException ignore) {
+                }
+            }
+            this.tmpFile.delete();
+        }
+    }
+}

Added: sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpServer.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpServer.java?rev=1436258&view=auto
==============================================================================
--- sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpServer.java (added)
+++ sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpServer.java Mon Jan 21 10:22:27 2013
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.ftpserver.impl;
+
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.ConfigurationPolicy;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.ftpserver.FtpServer;
+import org.apache.ftpserver.FtpServerFactory;
+import org.apache.ftpserver.ftplet.FtpException;
+import org.apache.ftpserver.ftplet.Ftplet;
+import org.apache.ftpserver.listener.ListenerFactory;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(
+        name = SlingConfiguration.NAME,
+        metatype = true,
+        policy = ConfigurationPolicy.REQUIRE,
+        label = "Apache Sling FTP Server",
+        description = "Provides FTP Server access to the Apache Sling resources")
+@Properties({
+    @Property(
+            name = SlingConfiguration.PROP_PORT,
+            intValue = SlingConfiguration.PROP_PORT_DEFAULT,
+            label = "Server Port",
+            description = "Port for the FTP Server to listen on. "
+                + "If zero a port is automatically selected (not recommended). Default value is "
+                + SlingConfiguration.PROP_PORT_DEFAULT),
+    @Property(
+            name = SlingConfiguration.PROP_ANONYMOUS_LOGINS,
+            boolValue = SlingConfiguration.PROP_ANONYMOUS_LOGINS_DEFAULT,
+            label = "Server Port",
+            description = "Is anonymous logins allowed at the server? " + "The default is "
+                + SlingConfiguration.PROP_ANONYMOUS_LOGINS_DEFAULT),
+    @Property(
+            name = SlingConfiguration.PROP_MAX_ANONYMOUS_LOGINS,
+            intValue = SlingConfiguration.PROP_MAX_ANONYMOUS_LOGINS_DEFAULT,
+            label = "Server Port",
+            description = "The maximum number of anonymous logins the server would allow at any given time. This property is only effective if anonymous login is enabled at all."
+                + "The default is " + SlingConfiguration.PROP_MAX_ANONYMOUS_LOGINS_DEFAULT),
+    @Property(
+            name = SlingConfiguration.PROP_MAX_LOGIN_FAILURES,
+            intValue = SlingConfiguration.PROP_MAX_LOGIN_FAILURES_DEFAULT,
+            label = "Server Port",
+            description = "The maximum number of time an user can fail to login before getting disconnected. "
+                + "The default is " + SlingConfiguration.PROP_MAX_LOGIN_FAILURES_DEFAULT),
+    @Property(
+            name = SlingConfiguration.PROP_LOGIN_FAILURE_DELAY,
+            intValue = SlingConfiguration.PROP_LOGIN_FAILURE_DELAY_DEFAUT,
+            label = "Server Port",
+            description = "The delay in number of milliseconds between login failures. Important to make brute force attacks harder. "
+                + "The default is " + SlingConfiguration.PROP_LOGIN_FAILURE_DELAY_DEFAUT),
+    @Property(
+            name = SlingConfiguration.PROP_MAX_LOGINS,
+            intValue = SlingConfiguration.PROP_MAX_LOGINS_DEFAULT,
+            label = "Server Port",
+            description = "Set the maximum number of concurrently logged in users. The default is "
+                + SlingConfiguration.PROP_MAX_LOGINS_DEFAULT),
+    @Property(
+            name = SlingConfiguration.PROP_MAX_THREADS,
+            intValue = SlingConfiguration.PROP_MAX_THREADS_DEFAULT,
+            label = "Server Port",
+            description = "Returns the maximum number of threads the server is allowed to create for processing client requests. "
+                + "The default is " + SlingConfiguration.PROP_MAX_THREADS_DEFAULT),
+    @Property(
+            name = SlingConfiguration.PROP_ENABLED,
+            boolValue = SlingConfiguration.PROP_ENABLED_DEFAULT,
+            label = "Enable Users",
+            description = "Wether repository users are enabled for FTP by default or not. If this property is set to true, users are by default enabled. "
+                + "This property can be configured on a per user basis setting the boolean "
+                + SlingUserManager.FTP_ENABLED
+                + " user property. Default value is "
+                + SlingConfiguration.PROP_ENABLED_DEFAULT),
+    @Property(
+            name = SlingConfiguration.PROP_MAX_IDLE_TIME,
+            intValue = SlingConfiguration.PROP_MAX_IDLE_TIME_DEFAULT,
+            label = "Default Idle Time",
+            description = "The default value for maximum time the FTP session may be idle before the server closing it. "
+                + "Setting this to zero disables idle session time (which is not recommended). "
+                + "This property can be configured on a per user basis setting the numeric"
+                + SlingUserManager.FTP_MAX_IDLE_TIME_SEC
+                + " user property. The default value is "
+                + SlingConfiguration.PROP_MAX_IDLE_TIME_DEFAULT + " seconds"),
+    @Property(
+            name = SlingConfiguration.PROP_MAX_CONCURRENT,
+            intValue = SlingConfiguration.PROP_MAX_CONCURRENT_DEFAULT,
+            label = "Default Concurrent Sessions",
+            description = "The default value for the maximum number of concurrent sessions for a single user. "
+                + "Setting this to zero disables this limitation (which is not recommended). "
+                + "This property can be configured on a per user basis setting the numeric "
+                + SlingUserManager.FTP_MAX_CONCURRENT + " user property. The default value is "
+                + SlingConfiguration.PROP_MAX_CONCURRENT_DEFAULT),
+    @Property(
+            name = SlingConfiguration.PROP_MAX_CONCURRENT_IP,
+            intValue = SlingConfiguration.PROP_MAX_CONCURRENT_IP_DEFAULT,
+            label = "Default Concurrent Sessions per IP Address",
+            description = "The default value for the maximum number of concurrent sessions for a single user from the same client IP address. "
+                + "Setting this to zero disables this limitation (which is not recommended). "
+                + "This property can be configured on a per user basis setting the numeric "
+                + SlingUserManager.FTP_MAX_CONCURRENT_PER_IP
+                + " user property. The default value is "
+                + SlingConfiguration.PROP_MAX_CONCURRENT_IP_DEFAULT),
+    @Property(
+            name = SlingConfiguration.PROP_MAX_DOWNLOAD,
+            intValue = SlingConfiguration.PROP_MAX_DOWNLOAD_DEFAULT,
+            label = "Default Download Data Rate",
+            description = "The default value for maximum file download data rate in 1000 bytes per second. "
+                + "Setting this to zero disables this limitation. "
+                + "This property can be configured on a per user basis setting the numeric "
+                + SlingUserManager.FTP_MAX_DOWNLOAD_RATE + " user property. The default value is "
+                + SlingConfiguration.PROP_MAX_DOWNLOAD_DEFAULT),
+    @Property(
+            name = SlingConfiguration.PROP_MAX_UPLOAD,
+            intValue = SlingConfiguration.PROP_MAX_UPLOAD_DEFAULT,
+            label = "Default Upload Data Rate",
+            description = "The default value for maximum file upload data rate in 1000 bytes per second. "
+                + "Setting this to zero disables this limitation. "
+                + "This property can be configured on a per user basis setting the numeric "
+                + SlingUserManager.FTP_MAX_UPLOAD_RATE + " user property. The default value is "
+                + SlingConfiguration.PROP_MAX_UPLOAD_DEFAULT)
+})
+public class SlingFtpServer {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Reference
+    private ResourceResolverFactory rrFactory;
+
+    private SlingConfiguration configuration;
+
+    private SlingUserManager userManager;
+
+    private FtpServer ftpServer;
+
+    private ServiceRegistration webConsolePlugin;
+
+    @SuppressWarnings("serial")
+    @Activate
+    private void activate(final BundleContext bundleContext, final Map<String, Object> config) {
+
+        // Consider not using the FtpServerFactory but directly create the
+        // FtpServer from our own custom FtpServerContext
+
+        this.configuration = new SlingConfiguration(config);
+        this.userManager = new SlingUserManager(this.rrFactory, this.configuration);
+
+        final ListenerFactory listenerFactory = new ListenerFactory();
+        listenerFactory.setPort(this.configuration.getPort());
+
+        final SlingFtpletProxy wcPlugin = new SlingFtpletProxy();
+
+        final FtpServerFactory factory = new FtpServerFactory();
+        factory.setFileSystem(new SlingFileSystemFactory());
+        factory.setUserManager(this.userManager);
+        factory.setConnectionConfig(this.configuration);
+        factory.addListener("default", listenerFactory.createListener());
+        factory.setFtplets(new HashMap<String, Ftplet>() {
+            {
+                put("sling", wcPlugin);
+            }
+        });
+
+        try {
+            final FtpServer ftpServer = factory.createServer();
+            ftpServer.start();
+            this.ftpServer = ftpServer;
+        } catch (FtpException e) {
+            log.error("Cannot start FTP Server", e);
+        }
+
+        this.webConsolePlugin = bundleContext.registerService("javax.servlet.Servlet", new ServiceFactory() {
+
+            public Object getService(Bundle bundle, ServiceRegistration registration) {
+                final WebConsoleFtpLet tmp = new WebConsoleFtpLet();
+                wcPlugin.setDelegatee(tmp);
+                return tmp;
+            }
+
+            public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) {
+                wcPlugin.setDelegatee(null);
+            }
+
+        }, new Hashtable<String, Object>() {
+            {
+                put("felix.webconsole.label", "ftpserver");
+                put("felix.webconsole.title", "FTP Server");
+                put("felix.webconsole.category", "Sling");
+            }
+        });
+    }
+
+    @Deactivate
+    private void deactivate() {
+        if (this.webConsolePlugin != null) {
+            this.webConsolePlugin.unregister();
+            this.webConsolePlugin = null;
+        }
+
+        if (this.ftpServer != null) {
+            this.ftpServer.stop();
+            this.ftpServer = null;
+        }
+
+        if (this.userManager != null) {
+            this.userManager.dispose();
+            this.userManager = null;
+        }
+    }
+}

Added: sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpletProxy.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpletProxy.java?rev=1436258&view=auto
==============================================================================
--- sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpletProxy.java (added)
+++ sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingFtpletProxy.java Mon Jan 21 10:22:27 2013
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.ftpserver.impl;
+
+import java.io.IOException;
+
+import org.apache.ftpserver.ftplet.FtpException;
+import org.apache.ftpserver.ftplet.FtpReply;
+import org.apache.ftpserver.ftplet.FtpRequest;
+import org.apache.ftpserver.ftplet.FtpSession;
+import org.apache.ftpserver.ftplet.Ftplet;
+import org.apache.ftpserver.ftplet.FtpletContext;
+import org.apache.ftpserver.ftplet.FtpletResult;
+
+public class SlingFtpletProxy implements Ftplet {
+
+    private Ftplet delegatee;
+
+    private FtpletContext ftpletContext;
+
+    public void setDelegatee(Ftplet delegatee) {
+        if (this.delegatee != null) {
+            this.delegatee.destroy();
+            this.delegatee = null;
+        }
+
+        if (delegatee != null) {
+            this.delegatee = delegatee;
+
+            if (this.ftpletContext != null) {
+                try {
+                    this.delegatee.init(this.ftpletContext);
+                } catch (FtpException e) {
+                    // TODO log
+                }
+            }
+        }
+
+    }
+
+    public void init(FtpletContext ftpletContext) throws FtpException {
+        this.ftpletContext = ftpletContext;
+        if (this.delegatee != null) {
+            this.delegatee.init(ftpletContext);
+        }
+    }
+
+    public void destroy() {
+        if (this.delegatee != null) {
+            this.delegatee.destroy();
+        }
+        this.ftpletContext = null;
+    }
+
+    public FtpletResult beforeCommand(FtpSession session, FtpRequest request) throws FtpException, IOException {
+        if (this.delegatee != null) {
+            return this.delegatee.beforeCommand(session, request);
+        }
+        return null;
+    }
+
+    public FtpletResult afterCommand(FtpSession session, FtpRequest request, FtpReply reply) throws FtpException,
+            IOException {
+        if (this.delegatee != null) {
+            return this.delegatee.afterCommand(session, request, reply);
+        }
+        return null;
+    }
+
+    public FtpletResult onConnect(FtpSession session) throws FtpException, IOException {
+        if (this.delegatee != null) {
+            return this.delegatee.onConnect(session);
+        }
+        return null;
+    }
+
+    public FtpletResult onDisconnect(FtpSession session) throws FtpException, IOException {
+        if (this.delegatee != null) {
+            return this.delegatee.onDisconnect(session);
+        }
+        return null;
+    }
+
+}

Added: sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingUser.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingUser.java?rev=1436258&view=auto
==============================================================================
--- sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingUser.java (added)
+++ sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingUser.java Mon Jan 21 10:22:27 2013
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.ftpserver.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.ftpserver.ftplet.Authority;
+import org.apache.ftpserver.ftplet.AuthorizationRequest;
+import org.apache.ftpserver.ftplet.User;
+import org.apache.sling.api.resource.ResourceResolver;
+
+public class SlingUser implements User {
+
+    private final ResourceResolver resolver;
+
+    private final String name;
+
+    private int maxIdleTimeSec = 0; // no limit
+
+    private boolean isEnabled = true;
+
+    private List<Authority> authorities;
+
+    SlingUser(final String name, final ResourceResolver resolver) {
+        this.resolver = resolver;
+        this.name = name;
+    }
+
+    public ResourceResolver getResolver() {
+        return resolver;
+    }
+
+    public AuthorizationRequest authorize(AuthorizationRequest request) {
+        // check for no authorities at all
+        if (authorities == null) {
+            return null;
+        }
+
+        boolean someoneCouldAuthorize = false;
+        for (Authority authority : authorities) {
+            if (authority.canAuthorize(request)) {
+                someoneCouldAuthorize = true;
+
+                request = authority.authorize(request);
+
+                // authorization failed, return null
+                if (request == null) {
+                    return null;
+                }
+            }
+
+        }
+
+        if (someoneCouldAuthorize) {
+            return request;
+        }
+
+        return null;
+    }
+
+    public List<Authority> getAuthorities() {
+        return this.authorities;
+    }
+
+    public void setAuthorities(List<Authority> authorities) {
+        if (authorities != null) {
+            this.authorities = Collections.unmodifiableList(authorities);
+        } else {
+            this.authorities = null;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<Authority> getAuthorities(Class<? extends Authority> clazz) {
+        List<Authority> selected = new ArrayList<Authority>();
+
+        for (Authority authority : authorities) {
+            if (authority.getClass().equals(clazz)) {
+                selected.add(authority);
+            }
+        }
+
+        return selected;
+    }
+
+    public void setEnabled(boolean isEnabled) {
+        this.isEnabled = isEnabled;
+    }
+
+    public boolean getEnabled() {
+        return this.isEnabled;
+    }
+
+    public String getHomeDirectory() {
+        return "/";
+    }
+
+    public void setMaxIdleTimeSec(int maxIdleTimeSec) {
+        this.maxIdleTimeSec = maxIdleTimeSec;
+    }
+
+    public int getMaxIdleTime() {
+        return this.maxIdleTimeSec;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getPassword() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+}

Added: sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingUserManager.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingUserManager.java?rev=1436258&view=auto
==============================================================================
--- sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingUserManager.java (added)
+++ sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingUserManager.java Mon Jan 21 10:22:27 2013
@@ -0,0 +1,227 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.ftpserver.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import org.apache.ftpserver.ftplet.Authentication;
+import org.apache.ftpserver.ftplet.AuthenticationFailedException;
+import org.apache.ftpserver.ftplet.Authority;
+import org.apache.ftpserver.ftplet.FtpException;
+import org.apache.ftpserver.ftplet.User;
+import org.apache.ftpserver.ftplet.UserManager;
+import org.apache.ftpserver.usermanager.AnonymousAuthentication;
+import org.apache.ftpserver.usermanager.UsernamePasswordAuthentication;
+import org.apache.ftpserver.usermanager.impl.ConcurrentLoginPermission;
+import org.apache.ftpserver.usermanager.impl.TransferRatePermission;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+
+public class SlingUserManager implements UserManager {
+
+    static final String FTP_ENABLED = "ftp/enabled";
+
+    static final String FTP_MAX_IDLE_TIME_SEC = "ftp/maxIdleTimeSec";
+
+    static final String FTP_MAX_CONCURRENT = "ftp/maxConcurrent";
+
+    static final String FTP_MAX_CONCURRENT_PER_IP = "ftp/maxConcurrentPerIp";
+
+    static final String FTP_MAX_DOWNLOAD_RATE = "ftp/maxDownloadRate";
+
+    static final String FTP_MAX_UPLOAD_RATE = "ftp/maxUploadRate";
+
+    private final ResourceResolverFactory rrFactory;
+
+    private final SlingConfiguration config;
+
+    private ResourceResolver admin;
+
+    SlingUserManager(final ResourceResolverFactory rrFactory, final SlingConfiguration config) {
+        this.rrFactory = rrFactory;
+        this.config = config;
+    }
+
+    void dispose() {
+        if (this.admin != null) {
+            this.admin.close();
+            this.admin = null;
+        }
+    }
+
+    public User authenticate(Authentication auth) throws AuthenticationFailedException {
+        if (auth == null) {
+            throw new AuthenticationFailedException("Missing Authentication");
+        }
+
+        final ResourceResolver resolver;
+        try {
+            if (auth instanceof AnonymousAuthentication) {
+                resolver = this.rrFactory.getResourceResolver(null);
+            } else if (auth instanceof UsernamePasswordAuthentication) {
+                final Map<String, Object> creds = new HashMap<String, Object>();
+                creds.put(ResourceResolverFactory.USER, ((UsernamePasswordAuthentication) auth).getUsername());
+                creds.put(ResourceResolverFactory.PASSWORD,
+                    ((UsernamePasswordAuthentication) auth).getPassword().toCharArray());
+                resolver = this.rrFactory.getResourceResolver(creds);
+            } else {
+                resolver = null;
+            }
+
+            if (resolver != null) {
+                return createUser(resolver.adaptTo(org.apache.jackrabbit.api.security.user.User.class), resolver);
+            }
+        } catch (RepositoryException e) {
+            throw new AuthenticationFailedException("Cannot login user " + auth, e);
+        } catch (LoginException e) {
+            throw new AuthenticationFailedException("Cannot login user " + auth, e);
+        }
+
+        // unsupported authentication
+        throw new AuthenticationFailedException("Authentication of type " + auth.getClass() + " not supported");
+    }
+
+    public void delete(String name) {
+        throw new UnsupportedOperationException("delete");
+    }
+
+    public boolean doesExist(String name) throws FtpException {
+        return getUserByName(name) != null;
+    }
+
+    public String getAdminName() throws FtpException {
+        return this.getAdminResolver().getUserID();
+    }
+
+    public String[] getAllUserNames() throws FtpException {
+        org.apache.jackrabbit.api.security.user.UserManager um = getAdminResolver().adaptTo(
+            org.apache.jackrabbit.api.security.user.UserManager.class);
+        if (um != null) {
+            try {
+                Iterator<Authorizable> ai = um.findAuthorizables(null, null,
+                    org.apache.jackrabbit.api.security.user.UserManager.SEARCH_TYPE_USER);
+                ArrayList<String> list = new ArrayList<String>();
+                while (ai.hasNext()) {
+                    list.add(ai.next().getID());
+                }
+                return list.toArray(new String[list.size()]);
+            } catch (RepositoryException e) {
+                throw new FtpException(e);
+            }
+        }
+
+        // is this correct ??
+        return new String[0];
+    }
+
+    public User getUserByName(String name) throws FtpException {
+        org.apache.jackrabbit.api.security.user.UserManager um = getAdminResolver().adaptTo(
+            org.apache.jackrabbit.api.security.user.UserManager.class);
+        if (um != null) {
+            Authorizable a;
+            try {
+                a = um.getAuthorizable(name);
+                if (a != null && !a.isGroup()) {
+                    return createUser((org.apache.jackrabbit.api.security.user.User) a, null);
+                }
+            } catch (RepositoryException e) {
+                throw new FtpException(e);
+            }
+        }
+
+        // is this correct ??
+        return null;
+    }
+
+    public boolean isAdmin(String id) throws FtpException {
+        return id != null && id.equals(getAdminName());
+    }
+
+    public void save(User user) {
+        throw new UnsupportedOperationException("save");
+    }
+
+    private ResourceResolver getAdminResolver() throws FtpException {
+        if (this.admin == null) {
+            try {
+                this.admin = this.rrFactory.getAdministrativeResourceResolver(null);
+            } catch (LoginException e) {
+                throw new FtpException("Cannot create administrative ResourceResolver", e);
+            }
+        }
+
+        return this.admin;
+    }
+
+    private User createUser(final org.apache.jackrabbit.api.security.user.User repoUser, final ResourceResolver resolver)
+            throws RepositoryException {
+        SlingUser user = new SlingUser(repoUser.getID(), resolver);
+
+        user.setEnabled(getProperty(repoUser, FTP_ENABLED, config.isEnabled()));
+        user.setMaxIdleTimeSec(getProperty(repoUser, FTP_MAX_IDLE_TIME_SEC, config.getMaxIdelTimeSec()));
+
+        List<Authority> list = new ArrayList<Authority>();
+        list.add(new ConcurrentLoginPermission(//
+            getProperty(repoUser, FTP_MAX_CONCURRENT, config.getMaxConcurrent()),//
+            getProperty(repoUser, FTP_MAX_CONCURRENT_PER_IP, config.getMaxConcurrentPerIp())));
+        list.add(new TransferRatePermission(//
+            getProperty(repoUser, FTP_MAX_DOWNLOAD_RATE, config.getMaxDownloadRate()),//
+            getProperty(repoUser, FTP_MAX_UPLOAD_RATE, config.getMaxUploadRate())));
+        list.add(new SlingWritePermission(resolver));
+
+        user.setAuthorities(list);
+
+        return user;
+    }
+
+    private int getProperty(final Authorizable a, final String prop, final int defaultValue) {
+        try {
+            Value[] vals = a.getProperty(prop);
+            if (vals != null && vals.length > 0) {
+                return (int) vals[0].getLong();
+            }
+        } catch (RepositoryException re) {
+            // ignore
+        }
+
+        return defaultValue;
+    }
+
+    private boolean getProperty(final Authorizable a, final String prop, final boolean defaultValue) {
+        try {
+            Value[] vals = a.getProperty(prop);
+            if (vals != null && vals.length > 0) {
+                return vals[0].getBoolean();
+            }
+        } catch (RepositoryException re) {
+            // ignore
+        }
+
+        return defaultValue;
+    }
+}

Added: sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingWritePermission.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingWritePermission.java?rev=1436258&view=auto
==============================================================================
--- sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingWritePermission.java (added)
+++ sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/SlingWritePermission.java Mon Jan 21 10:22:27 2013
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.ftpserver.impl;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.Privilege;
+
+import org.apache.ftpserver.ftplet.Authority;
+import org.apache.ftpserver.ftplet.AuthorizationRequest;
+import org.apache.ftpserver.usermanager.impl.WriteRequest;
+import org.apache.sling.api.resource.ResourceResolver;
+
+public class SlingWritePermission implements Authority {
+
+    private final AccessControlManager acm;
+
+    private final Privilege[] write;
+
+    public SlingWritePermission(final ResourceResolver resolver) {
+        AccessControlManager acm = null;
+        Privilege[] write = null;
+
+        if (resolver != null) {
+            try {
+                final Session session = resolver.adaptTo(Session.class);
+                if (session != null) {
+                    acm = session.getAccessControlManager();
+                    write = new Privilege[] {
+                        acm.privilegeFromName(Privilege.JCR_WRITE)
+                    };
+                }
+            } catch (RepositoryException re) {
+                // TODO: log
+                acm = null;
+                write = null;
+            }
+        }
+
+        this.acm = acm;
+        this.write = write;
+    }
+
+    public boolean canAuthorize(final AuthorizationRequest request) {
+        return request instanceof WriteRequest;
+    }
+
+    public AuthorizationRequest authorize(final AuthorizationRequest request) {
+        if ((request instanceof WriteRequest) && this.acm != null) {
+            WriteRequest writeRequest = (WriteRequest) request;
+            String requestFile = writeRequest.getFile();
+
+            try {
+                if (this.acm.hasPrivileges(requestFile, this.write)) {
+                    return writeRequest;
+                }
+            } catch (RepositoryException e) {
+                // TODO Auto-generated catch block
+            }
+        }
+
+        return null;
+    }
+
+}

Added: sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/WebConsoleFtpLet.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/WebConsoleFtpLet.java?rev=1436258&view=auto
==============================================================================
--- sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/WebConsoleFtpLet.java (added)
+++ sling/whiteboard/fmeschbe/ftpserver/src/main/java/org/apache/sling/ftpserver/impl/WebConsoleFtpLet.java Mon Jan 21 10:22:27 2013
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.ftpserver.impl;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Date;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.ftpserver.ftplet.FtpReply;
+import org.apache.ftpserver.ftplet.FtpRequest;
+import org.apache.ftpserver.ftplet.FtpSession;
+import org.apache.ftpserver.ftplet.FtpStatistics;
+import org.apache.ftpserver.ftplet.Ftplet;
+import org.apache.ftpserver.ftplet.FtpletContext;
+import org.apache.ftpserver.ftplet.FtpletResult;
+
+@SuppressWarnings("serial")
+public class WebConsoleFtpLet extends HttpServlet implements Ftplet {
+
+    private FtpStatistics stats;
+
+    private final ConcurrentHashMap<UUID, SessionEntry> sessions = new ConcurrentHashMap<UUID, SessionEntry>();
+
+    public void init(FtpletContext ftpletContext) {
+        this.stats = ftpletContext.getFtpStatistics();
+    }
+
+    public FtpletResult onConnect(FtpSession session) {
+        this.sessions.put(session.getSessionId(), new SessionEntry(session));
+        return null;
+    }
+
+    public FtpletResult beforeCommand(FtpSession session, FtpRequest request) {
+        this.sessions.get(session.getSessionId()).setRequestLine(request.getRequestLine());
+        return null;
+    }
+
+    public FtpletResult afterCommand(FtpSession session, FtpRequest request, FtpReply reply) {
+        this.sessions.get(session.getSessionId()).setRequestLine(null);
+        return null;
+    }
+
+    public FtpletResult onDisconnect(FtpSession session) {
+        this.sessions.remove(session.getSessionId());
+        return null;
+    }
+
+    public void destroy() {
+        // make sure to not hold on to anything after destroyal
+        this.sessions.clear();
+    }
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+
+        final PrintWriter pw = resp.getWriter();
+
+        final Date start = this.stats.getStartTime();
+        final long uptime = (System.currentTimeMillis() - start.getTime()) / 1000;
+        pw.print("<p class=\"statline ui-state-highlight\">");
+        pw.printf("FTP Server active. Started: %s. Uptime %d:%02d:%02d.", start, uptime / 3600, uptime / 60 % 60,
+            uptime % 60);
+        pw.println("</p>");
+
+        pw.println("<table class=\"nicetable\">");
+        pw.println("<tr>");
+        pw.println("<th>Connections</th>");
+        pw.println("<th>Anonymous Logins</th>");
+        pw.println("<th>Authenticated Logins</th>");
+        pw.println("<th>Total</th>");
+        pw.println("<th>Failed</th>");
+        pw.println("</tr>");
+        pw.println("<tr>");
+        pw.println("<th>Current</th>");
+        pw.printf("<td>%d</td>%n", this.stats.getCurrentAnonymousLoginNumber());
+        pw.printf("<td>%d</td>%n", this.stats.getCurrentLoginNumber());
+        pw.printf("<td>%d</td>%n", this.stats.getCurrentConnectionNumber());
+        pw.printf("<td>&nbsp</td>%n");
+        pw.println("</tr>");
+        pw.println("<tr>");
+        pw.println("<th>Total</th>");
+        pw.printf("<td>%d</td>%n", this.stats.getTotalAnonymousLoginNumber());
+        pw.printf("<td>%d</td>%n", this.stats.getTotalConnectionNumber());
+        pw.printf("<td>%d</td>%n", this.stats.getTotalLoginNumber());
+        pw.printf("<td>%d</td>%n", this.stats.getTotalFailedLoginNumber());
+        pw.println("</tr>");
+        pw.println("</table>");
+
+        pw.println("<p>&nbsp;</p>");
+
+        pw.println("<table class=\"nicetable\">");
+        pw.println("<tr>");
+        pw.println("<th colspan='2'>Operations</th>");
+        pw.println("</tr>");
+        pw.println("<tr>");
+        pw.println("<th>Directory</th>");
+        pw.printf("<td>%d created, %d removed</td>%n", this.stats.getTotalDirectoryCreated(),
+            this.stats.getTotalDirectoryRemoved());
+        pw.println("</tr>");
+        pw.println("<tr>");
+        pw.println("<th>File Removals</th>");
+        pw.printf("<td>%d</td>%n", this.stats.getTotalDeleteNumber());
+        pw.println("</tr>");
+        pw.println("<tr>");
+        pw.println("<th>File Uploads</th>");
+        pw.printf("<td>%d (%d MB)</td>%n", this.stats.getTotalUploadNumber(),
+            this.stats.getTotalUploadSize() / 1024 / 1024);
+        pw.println("</tr>");
+        pw.println("</tr>");
+        pw.println("<tr>");
+        pw.println("<th>File Downloads</th>");
+        pw.printf("<td>%d (%d MB)</td>%n", this.stats.getTotalDownloadNumber(),
+            this.stats.getTotalDownloadSize() / 1024 / 1024);
+        pw.println("</tr>");
+        pw.println("</table>");
+
+        pw.println("<p>&nbsp;</p>");
+
+        pw.println("<table class=\"nicetable\">");
+        pw.println("<tr>");
+        pw.println("<th>Session ID</th>");
+        pw.println("<th>User</th>");
+        pw.println("<th>Address</th>");
+        pw.println("<th>Connected</th>");
+        pw.println("<th>Logged In</th>");
+        pw.println("<th>Last Access</th>");
+        pw.println("<th>Max Idle Time (s)</th>");
+        pw.println("</tr>");
+        for (SessionEntry entry : this.sessions.values()) {
+            final FtpSession session = entry.getSession();
+
+            /*
+             * Display a subset of the session information. The following
+             * is omitted for now:
+             *  - session.getDataType();
+             *  - session.getFailedLogins();
+             *  - session.getFileOffset();
+             *  - session.getLanguage()
+             *  - session.getUserArgument();
+             *  - session.getStructure();
+             */
+
+            pw.println("<tr>");
+            pw.printf("<td>%s</td>%n", session.getSessionId());
+            pw.printf("<td>%s</td>%n", session.getUser().getName());
+            pw.printf("<td>%s:%s</td>%n", session.getClientAddress().getAddress().getHostAddress(),
+                session.getClientAddress().getPort());
+            pw.printf("<td>%1$tF %1$tT</td>%n", session.getConnectionTime());
+            pw.printf("<td>%1$tF %1$tT</td>%n", session.getLoginTime());
+            pw.printf("<td>%1$tF %1$tT</td>%n", session.getLastAccessTime());
+            pw.printf("<td>%d</td>%n", session.getMaxIdleTime());
+            pw.println("</tr>");
+
+            if (entry.getRequestLine() != null) {
+                pw.println("<tr>");
+                pw.printf("<td>&nbsp;</td>%n");
+                pw.printf("<td colspan='6'>%s</td>%n", entry.getRequestLine());
+                pw.println("</tr>");
+            }
+        }
+        pw.println("</table>");
+    }
+
+    private static final class SessionEntry {
+        private final FtpSession session;
+
+        private String requestLine;
+
+        public SessionEntry(final FtpSession session) {
+            this.session = session;
+        }
+
+        public FtpSession getSession() {
+            return session;
+        }
+
+        public void setRequestLine(String requestLine) {
+            this.requestLine = requestLine;
+        }
+
+        public String getRequestLine() {
+            return requestLine;
+        }
+    }
+}



Mime
View raw message