All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xbib.io.ftp.fs.FTPFileSystemProvider Maven / Gradle / Ivy

There is a newer version: 2.6.0
Show newest version
package org.xbib.io.ftp.fs;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.AccessMode;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemAlreadyExistsException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.ProviderMismatchException;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.attribute.FileOwnerAttributeView;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.GroupPrincipal;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.UserPrincipal;
import java.nio.file.spi.FileSystemProvider;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * A provider for FTP file systems.
 */
public class FTPFileSystemProvider extends FileSystemProvider {

    private final Map fileSystems = new HashMap<>();

    private static FTPPath toFTPPath(Path path) {
        Objects.requireNonNull(path);
        if (path instanceof FTPPath) {
            return (FTPPath) path;
        }
        throw new ProviderMismatchException();
    }

    /**
     * Send a keep-alive signal for an FTP file system.
     *
     * @param fs The FTP file system to send a keep-alive signal for.
     * @throws ProviderMismatchException If the given file system is not an FTP file system
     * (not created by an {@code FTPFileSystemProvider}).
     * @throws IOException               If an I/O error occurred.
     */
    public static void keepAlive(FileSystem fs) throws IOException {
        if (fs instanceof FTPFileSystem) {
            ((FTPFileSystem) fs).keepAlive();
        }
        throw new ProviderMismatchException();
    }

    /**
     * Returns the URI scheme that identifies this provider: {@code ftp}.
     */
    @Override
    public String getScheme() {
        return "ftp";
    }

    /**
     * Constructs a new {@code FileSystem} object identified by a URI.
     * 

* The URI must have a {@link URI#getScheme() scheme} equal to {@link #getScheme()}, * and no {@link URI#getUserInfo() user information}, * {@link URI#getPath() path}, {@link URI#getQuery() query} or {@link URI#getFragment() fragment}. * Authentication credentials must be set through * the given environment map, preferably through {@link FTPEnvironment}. *

* This provider allows multiple file systems per host, but only one file system per user on a host. * Once a file system is {@link FileSystem#close() closed}, this provider allows a new file system * to be created with the same URI and credentials * as the closed file system. */ @Override public FileSystem newFileSystem(URI uri, Map env) throws IOException { // user info must come from the environment map checkURI(uri, false, false); FTPEnvironment environment = wrapEnvironment(env); String username = environment.getUsername(); URI normalizedURI = normalizeWithUsername(uri, username); synchronized (fileSystems) { if (fileSystems.containsKey(normalizedURI)) { throw new FileSystemAlreadyExistsException(normalizedURI.toString()); } FTPFileSystem fs = new FTPFileSystem(this, normalizedURI, environment); fileSystems.put(normalizedURI, fs); return fs; } } FTPEnvironment wrapEnvironment(Map env) { return FTPEnvironment.wrap(env); } /** * Returns an existing {@code FileSystem} created by this provider. * The URI must have a {@link URI#getScheme() scheme} equal to {@link #getScheme()}, * and no {@link URI#getPath() path}, * {@link URI#getQuery() query} or {@link URI#getFragment() fragment}. * Because the original credentials were provided through an environment map, * the URI can contain {@link URI#getUserInfo() user information}, although this should not * contain a password for security reasons. * Once a file system is {@link FileSystem#close() closed}, * this provided will throw a {@link FileSystemNotFoundException}. */ @Override public FileSystem getFileSystem(URI uri) { checkURI(uri, true, false); return getExistingFileSystem(uri); } /** * Return a {@code Path} object by converting the given {@link URI}. The resulting {@code Path} * is associated with a {@link FileSystem} that * already exists. This method does not support constructing {@code FileSystem}s automatically. *

* The URI must have a {@link URI#getScheme() scheme} equal to {@link #getScheme()}, * and no {@link URI#getQuery() query} or * {@link URI#getFragment() fragment}. Because the original credentials were provided through an environment map, * the URI can contain {@link URI#getUserInfo() user information}, * although this should not contain a password for security reasons. */ @Override public Path getPath(URI uri) { checkURI(uri, true, true); FTPFileSystem fs = getExistingFileSystem(uri); return fs.getPath(uri.getPath()); } private FTPFileSystem getExistingFileSystem(URI uri) { URI normalizedURI = normalizeWithoutPassword(uri); synchronized (fileSystems) { FTPFileSystem fs = fileSystems.get(normalizedURI); if (fs == null) { throw new FileSystemNotFoundException(uri.toString()); } return fs; } } private void checkURI(URI uri, boolean allowUserInfo, boolean allowPath) { if (!uri.isAbsolute()) { throw Messages.uri().notAbsolute(uri); } if (!getScheme().equalsIgnoreCase(uri.getScheme())) { throw Messages.uri().invalidScheme(uri, getScheme()); } if (!allowUserInfo && uri.getUserInfo() != null && !uri.getUserInfo().isEmpty()) { throw Messages.uri().hasUserInfo(uri); } if (uri.isOpaque()) { throw Messages.uri().notHierarchical(uri); } if (!allowPath && uri.getPath() != null && !uri.getPath().isEmpty()) { throw Messages.uri().hasPath(uri); } if (uri.getQuery() != null && !uri.getQuery().isEmpty()) { throw Messages.uri().hasQuery(uri); } if (uri.getFragment() != null && !uri.getFragment().isEmpty()) { throw Messages.uri().hasFragment(uri); } } void removeFileSystem(URI uri) { URI normalizedURI = normalizeWithoutPassword(uri); synchronized (fileSystems) { fileSystems.remove(normalizedURI); } } private URI normalizeWithoutPassword(URI uri) { String userInfo = uri.getUserInfo(); if (userInfo == null && uri.getPath() == null && uri.getQuery() == null && uri.getFragment() == null) { // nothing to normalize, return the URI return uri; } String username = null; if (userInfo != null) { int index = userInfo.indexOf(':'); username = index == -1 ? userInfo : userInfo.substring(0, index); } // no path, query or fragment return URISupport.create(uri.getScheme(), username, uri.getHost(), uri.getPort(), null, null, null); } private URI normalizeWithUsername(URI uri, String username) { if (username == null && uri.getUserInfo() == null && uri.getPath() == null && uri.getQuery() == null && uri.getFragment() == null) { // nothing to normalize or add, return the URI return uri; } // no path, query or fragment return URISupport.create(uri.getScheme(), username, uri.getHost(), uri.getPort(), null, null, null); } /** * Opens a file, returning an input stream to read from the file. * This method works in exactly the manner specified by the {@link Files#newInputStream(Path, OpenOption...)} method. *

* In addition to the standard open options, this method also supports single occurrences of each of * {@link FileType}, {@link FileStructure} and * {@link FileTransferMode}. These three option types, with defaults of {@link FileType#binary()}, * {@link FileStructure#FILE} and * {@link FileTransferMode#STREAM}, persist for all calls that support file transfers: *

    *
  • {@link #newInputStream(Path, OpenOption...)}
  • *
  • {@link #newOutputStream(Path, OpenOption...)}
  • *
  • {@link #newByteChannel(Path, Set, FileAttribute...)}
  • *
  • {@link #copy(Path, Path, CopyOption...)}
  • *
  • {@link #move(Path, Path, CopyOption...)}
  • *
*

* Note: while the returned input stream is not closed, the path's file system will have * one available connection fewer. * It is therefore essential that the input stream is closed as soon as possible. */ @Override public InputStream newInputStream(Path path, OpenOption... options) throws IOException { return toFTPPath(path).newInputStream(options); } /** * Opens or creates a file, returning an output stream that may be used to write bytes to the file. * This method works in exactly the manner specified by the {@link Files#newOutputStream(Path, OpenOption...)} method. *

* In addition to the standard open options, this method also supports single occurrences of each of * {@link FileType}, {@link FileStructure} and * {@link FileTransferMode}. These three option types, with defaults of {@link FileType#binary()}, * {@link FileStructure#FILE} and * {@link FileTransferMode#STREAM}, persist for all calls that support file transfers: *

    *
  • {@link #newInputStream(Path, OpenOption...)}
  • *
  • {@link #newOutputStream(Path, OpenOption...)}
  • *
  • {@link #newByteChannel(Path, Set, FileAttribute...)}
  • *
  • {@link #copy(Path, Path, CopyOption...)}
  • *
  • {@link #move(Path, Path, CopyOption...)}
  • *
*

* Note: while the returned output stream is not closed, the path's file system will have one available * connection fewer. * It is therefore essential that the output stream is closed as soon as possible. */ @Override public OutputStream newOutputStream(Path path, OpenOption... options) throws IOException { return toFTPPath(path).newOutputStream(options); } /** * Opens or creates a file, returning a seekable byte channel to access the file. * This method works in exactly the manner specified by the * {@link Files#newByteChannel(Path, Set, FileAttribute...)} method. *

* In addition to the standard open options, this method also supports single occurrences of * each of {@link FileType}, {@link FileStructure} and * {@link FileTransferMode}. These three option types, with defaults of {@link FileType#binary()}, * {@link FileStructure#FILE} and * {@link FileTransferMode#STREAM}, persist for all calls that support file transfers: *

    *
  • {@link #newInputStream(Path, OpenOption...)}
  • *
  • {@link #newOutputStream(Path, OpenOption...)}
  • *
  • {@link #newByteChannel(Path, Set, FileAttribute...)}
  • *
  • {@link #copy(Path, Path, CopyOption...)}
  • *
  • {@link #move(Path, Path, CopyOption...)}
  • *
*

* This method does not support any file attributes to be set. If any file attributes are given, * an {@link UnsupportedOperationException} will be * thrown. *

* Note: while the returned channel is not closed, the path's file system will have one available connection fewer. * It is therefore essential that the channel is closed as soon as possible. */ @Override public SeekableByteChannel newByteChannel(Path path, Set options, FileAttribute... attrs) throws IOException { return toFTPPath(path).newByteChannel(options, attrs); } @Override public DirectoryStream newDirectoryStream(Path dir, Filter filter) throws IOException { return toFTPPath(dir).newDirectoryStream(filter); } /** * Creates a new directory. * This method works in exactly the manner specified by the * {@link Files#createDirectory(Path, FileAttribute...)} method. *

* This method does not support any file attributes to be set. * If any file attributes are given, an {@link UnsupportedOperationException} will be * thrown. */ @Override public void createDirectory(Path dir, FileAttribute... attrs) throws IOException { toFTPPath(dir).createDirectory(attrs); } @Override public void delete(Path path) throws IOException { toFTPPath(path).delete(); } @Override public Path readSymbolicLink(Path link) throws IOException { return toFTPPath(link).readSymbolicLink(); } /** * Copy a file to a target file. * This method works in exactly the manner specified by the {@link Files#copy(Path, Path, CopyOption...)} * method except that both the source and * target paths must be associated with this provider. *

* In addition to the standard copy options, this method also supports single occurrences of each of * {@link FileType}, {@link FileStructure} and * {@link FileTransferMode}. These three option types, with defaults of {@link FileType#binary()}, * {@link FileStructure#FILE} and * {@link FileTransferMode#STREAM}, persist for all calls that support file transfers: *

    *
  • {@link #newInputStream(Path, OpenOption...)}
  • *
  • {@link #newOutputStream(Path, OpenOption...)}
  • *
  • {@link #newByteChannel(Path, Set, FileAttribute...)}
  • *
  • {@link #copy(Path, Path, CopyOption...)}
  • *
  • {@link #move(Path, Path, CopyOption...)}
  • *
*

* {@link StandardCopyOption#COPY_ATTRIBUTES} and {@link StandardCopyOption#ATOMIC_MOVE} are not supported though. */ @Override public void copy(Path source, Path target, CopyOption... options) throws IOException { toFTPPath(source).copy(toFTPPath(target), options); } /** * Move or rename a file to a target file. * This method works in exactly the manner specified by the {@link Files#move(Path, Path, CopyOption...)} * method except that both the source and * target paths must be associated with this provider. *

* In addition to the standard copy options, this method also supports single occurrences of each of * {@link FileType}, {@link FileStructure} and * {@link FileTransferMode}. These three option types, with defaults of {@link FileType#binary()}, * {@link FileStructure#FILE} and * {@link FileTransferMode#STREAM}, persist for all calls that support file transfers: *

    *
  • {@link #newInputStream(Path, OpenOption...)}
  • *
  • {@link #newOutputStream(Path, OpenOption...)}
  • *
  • {@link #newByteChannel(Path, Set, FileAttribute...)}
  • *
  • {@link #copy(Path, Path, CopyOption...)}
  • *
  • {@link #move(Path, Path, CopyOption...)}
  • *
*

* {@link StandardCopyOption#COPY_ATTRIBUTES} is not supported though. * {@link StandardCopyOption#ATOMIC_MOVE} is only supported if the paths have * the same file system. */ @Override public void move(Path source, Path target, CopyOption... options) throws IOException { toFTPPath(source).move(toFTPPath(target), options); } @Override public boolean isSameFile(Path path, Path path2) throws IOException { return toFTPPath(path).isSameFile(path2); } @Override public boolean isHidden(Path path) throws IOException { return toFTPPath(path).isHidden(); } @Override public FileStore getFileStore(Path path) throws IOException { return toFTPPath(path).getFileStore(); } @Override public void checkAccess(Path path, AccessMode... modes) throws IOException { toFTPPath(path).checkAccess(modes); } /** * Returns a file attribute view of a given type. * This method works in exactly the manner specified by the * {@link Files#getFileAttributeView(Path, Class, LinkOption...)} method. *

* This provider supports {@link BasicFileAttributeView}, {@link FileOwnerAttributeView} and * {@link PosixFileAttributeView}. * All other classes will result in a {@code null} return value. *

* Note that the returned {@link FileAttributeView} is read-only; any attempt to change any attributes * through the view will result in an * {@link UnsupportedOperationException} to be thrown. */ @Override public V getFileAttributeView(Path path, Class type, LinkOption... options) { Objects.requireNonNull(type); if (type == BasicFileAttributeView.class) { return type.cast(new AttributeView("basic", toFTPPath(path))); } if (type == FileOwnerAttributeView.class) { return type.cast(new AttributeView("owner", toFTPPath(path))); } if (type == PosixFileAttributeView.class) { return type.cast(new AttributeView("posix", toFTPPath(path))); } return null; } /** * Reads a file's attributes as a bulk operation. * This method works in exactly the manner specified by the * {@link Files#readAttributes(Path, Class, LinkOption...)} method. * This provider supports {@link BasicFileAttributes} and {@link PosixFileAttributes} * (there is no {@code FileOwnerFileAttributes}). * All other classes will result in an {@link UnsupportedOperationException} to be thrown. */ @Override public A readAttributes(Path path, Class type, LinkOption... options) throws IOException { if (type == BasicFileAttributes.class || type == PosixFileAttributes.class) { return type.cast(toFTPPath(path).readAttributes(options)); } throw Messages.fileSystemProvider().unsupportedFileAttributesType(type); } /** * Reads a set of file attributes as a bulk operation. * This method works in exactly the manner specified by the {@link Files#readAttributes(Path, String, LinkOption...)} method. *

* This provider supports views {@code basic}, {@code owner} and {code posix}, where {@code basic} will be used if no view is given. * All other views will result in an {@link UnsupportedOperationException} to be thrown. */ @Override public Map readAttributes(Path path, String attributes, LinkOption... options) throws IOException { return toFTPPath(path).readAttributes(attributes, options); } /** * Sets the value of a file attribute. * This method works in exactly the manner specified by the {@link Files#setAttribute(Path, String, Object, LinkOption...)} method. *

* This provider does not support attributes for paths to be set. This method will always throw an {@link UnsupportedOperationException}. */ @Override public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException { throw Messages.unsupportedOperation(FileSystemProvider.class, "setAttribute"); } private static final class AttributeView implements PosixFileAttributeView { private final String name; private final FTPPath path; private AttributeView(String name, FTPPath path) { this.name = Objects.requireNonNull(name); this.path = Objects.requireNonNull(path); } @Override public String name() { return name; } @Override public UserPrincipal getOwner() throws IOException { return readAttributes().owner(); } @Override public void setOwner(UserPrincipal owner) throws IOException { throw Messages.unsupportedOperation(FileOwnerAttributeView.class, "setOwner"); } @Override public PosixFileAttributes readAttributes() throws IOException { return path.readAttributes(); } @Override public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException { throw Messages.unsupportedOperation(BasicFileAttributeView.class, "setTimes"); } @Override public void setGroup(GroupPrincipal group) throws IOException { throw Messages.unsupportedOperation(PosixFileAttributeView.class, "setGroup"); } @Override public void setPermissions(Set perms) throws IOException { throw Messages.unsupportedOperation(PosixFileAttributeView.class, "setPermissions"); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy