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

software.amazon.nio.spi.s3.S3FileSystemProvider Maven / Gradle / Ivy

Go to download

A Java NIO.2 service provider for S3, allowing Java NIO operations to be performed on paths using the `s3` scheme. This package implements the service provider interface (SPI) defined for Java NIO.2 in JDK 1.7 providing "plug-in" non-blocking access to S3 objects for Java applications using Java NIO.2 for file access.

There is a newer version: 2.2.0
Show newest version
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package software.amazon.nio.spi.s3;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.http.SdkHttpResponse;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.model.*;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.CopyRequest;
import software.amazon.nio.spi.s3.util.TimeOutUtils;

import java.io.IOException;
import java.net.URI;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.*;
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.spi.FileSystemProvider;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static software.amazon.awssdk.http.HttpStatusCode.FORBIDDEN;
import static software.amazon.awssdk.http.HttpStatusCode.NOT_FOUND;
import static software.amazon.nio.spi.s3.util.TimeOutUtils.TIMEOUT_TIME_LENGTH_1;
import static software.amazon.nio.spi.s3.util.TimeOutUtils.logAndGenerateExceptionOnTimeOut;

/**
 * Service-provider class for S3 when represented as an NIO filesystem. The methods defined by the Files class will
 * delegate to an instance of this class when referring to an object in S3. This class will in turn make calls to the
 * S3 service.
 * 
* This class should never be used directly. It is invoked by the service loader when, for example, the java.nio.file.Files * class is used to address an object beginning with the scheme "s3". */ public class S3FileSystemProvider extends FileSystemProvider { /** * Constant for the S3 scheme "s3" */ public static final String SCHEME = "s3"; private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); /** * Returns the URI scheme that identifies this provider. * * @return The URI scheme (s3) */ @Override public String getScheme() { return SCHEME; } /** * Constructs a new {@code FileSystem} object identified by a URI. This * method is invoked by the {@link FileSystems#newFileSystem(URI, Map)} * method to open a new file system identified by a URI. * *

The {@code uri} parameter is an absolute, hierarchical URI, with a * scheme equal (without regard to case) to the scheme supported by this * provider. The exact form of the URI is highly provider dependent. The * {@code env} parameter is a map of provider specific properties to configure * the file system. * *

This method throws {@link FileSystemAlreadyExistsException} if the * file system already exists because it was previously created by an * invocation of this method. Once a file system is {@link * FileSystem#close closed} it is provider-dependent if the * provider allows a new file system to be created with the same URI as a * file system it previously created. * * @param uri URI reference * @param env A map of provider specific properties to configure the file system; * may be empty * @return A new file system */ @Override public FileSystem newFileSystem(URI uri, Map env) { return new S3FileSystem(uri, this); } /** * Returns an existing {@code FileSystem} created by this provider. * *

This method returns a reference to a {@code FileSystem} that was * created by invoking the {@link #newFileSystem(URI, Map) newFileSystem(URI,Map)} * method. File systems created the {@link #newFileSystem(Path, Map) * newFileSystem(Path,Map)} method are not returned by this method. * The file system is identified by its {@code URI}. Its exact form * is highly provider dependent. In the case of the default provider the URI's * path component is {@code "/"} and the authority, query and fragment components * are undefined (Undefined components are represented by {@code null}). * *

Once a file system created by this provider is {@link * FileSystem#close closed} it is provider-dependent if this * method returns a reference to the closed file system or throws {@link * FileSystemNotFoundException}. If the provider allows a new file system to * be created with the same URI as a file system it previously created then * this method throws the exception if invoked after the file system is * closed (and before a new instance is created by the {@link #newFileSystem * newFileSystem} method). * *

If a security manager is installed then a provider implementation * may require to check a permission before returning a reference to an * existing file system. In the case of the {@link FileSystems#getDefault * default} file system, no permission check is required. * * @param uri URI reference * @return The file system * @throws IllegalArgumentException If the pre-conditions for the {@code uri} parameter aren't met * @throws FileSystemNotFoundException If the file system does not exist * @throws SecurityException If a security manager is installed, and it denies an unspecified * permission. */ @Override public S3FileSystem getFileSystem(URI uri) { return new S3FileSystem(uri, this); } /** * Return a {@code Path} object by converting the given {@link URI}. The * resulting {@code Path} is associated with a {@link FileSystem} that * already exists or is constructed automatically. * *

The exact form of the URI is file system provider dependent. In the * case of the default provider, the URI scheme is {@code "file"} and the * given URI has a non-empty path component, and undefined query, and * fragment components. The resulting {@code Path} is associated with the * default {@link FileSystems#getDefault default} {@code FileSystem}. * *

If a security manager is installed then a provider implementation * may require to check a permission. In the case of the {@link * FileSystems#getDefault default} file system, no permission check is * required. * * @param uri The URI to convert. Must not be null. * @return The resulting {@code Path} * @throws IllegalArgumentException If the URI scheme does not identify this provider or other * preconditions on the uri parameter do not hold * @throws FileSystemNotFoundException The file system, identified by the URI, does not exist and * cannot be created automatically * @throws SecurityException If a security manager is installed, and it denies an unspecified * permission. */ @Override public S3Path getPath(URI uri) { Objects.requireNonNull(uri); return ((S3FileSystem) FileSystems.getFileSystem(uri)).getPath(uri.getPath()); } /** * 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. * * @param path the path to the file to open or create * @param options options specifying how the file is opened * @param attrs an optional list of file attributes to set atomically when * creating the file * @return a new seekable byte channel * @throws IllegalArgumentException if the set contains an invalid combination of options * @throws UnsupportedOperationException if an unsupported open option is specified or the array contains * attributes that cannot be set atomically when creating the file * @throws FileAlreadyExistsException if a file of that name already exists and the {@link * StandardOpenOption#CREATE_NEW CREATE_NEW} option is specified * (optional specific exception) * @throws IOException if an I/O error occurs * @throws SecurityException In the case of the default provider, and a security manager is * installed, the {@link SecurityManager#checkRead(String) checkRead} * method is invoked to check read access to the path if the file is * opened for reading. The {@link SecurityManager#checkWrite(String) * checkWrite} method is invoked to check write access to the path * if the file is opened for writing. The {@link * SecurityManager#checkDelete(String) checkDelete} method is * invoked to check delete access if the file is opened with the * {@code DELETE_ON_CLOSE} option. */ @Override public SeekableByteChannel newByteChannel(Path path, Set options, FileAttribute... attrs) throws IOException { return this.newByteChannel(null, path, options, attrs); } /** * Construct a byte channel for the path with the specified client. A more composable and testable (by using a Mock Client) * version of the public method */ protected SeekableByteChannel newByteChannel(S3AsyncClient client, Path path, Set options, FileAttribute... attrs) throws IOException { if (Objects.isNull(options)) { options = Collections.emptySet(); } final S3Path s3Path = getPath(path.toUri()); final S3SeekableByteChannel channel; if (client == null) { client = S3ClientStore.getInstance().getAsyncClientForBucketName(s3Path.bucketName()); } channel = new S3SeekableByteChannel(s3Path, client, options); s3Path.getFileSystem().registerOpenChannel(channel); return channel; } /** * Opens a directory, returning a {@code DirectoryStream} to iterate over * the entries in the directory. This method works in exactly the manner * specified by the {@link * Files#newDirectoryStream(Path, DirectoryStream.Filter)} * method. * * @param dir the path to the directory * @param filter the directory stream filter * @return a new and open {@code DirectoryStream} object */ @Override public DirectoryStream newDirectoryStream(Path dir, DirectoryStream.Filter filter) throws IOException { try { return this.newDirectoryStream(null, dir, filter); } catch (ExecutionException e) { throw new IOException(e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } /** * Get a new directory stream that will use the specified client. A composable and testable version of the public * version of {@code newDirectoryStream} */ protected DirectoryStream newDirectoryStream(S3AsyncClient s3Client, Path dir, DirectoryStream.Filter filter) throws ExecutionException, InterruptedException { S3Path s3Path = (S3Path) dir; if (s3Client == null) { s3Client = S3ClientStore.getInstance().getAsyncClientForBucketName(s3Path.bucketName()); } String pathString = s3Path.toRealPath(NOFOLLOW_LINKS).getKey(); if (!pathString.endsWith(S3Path.PATH_SEPARATOR) && !pathString.isEmpty()) { pathString = pathString + S3Path.PATH_SEPARATOR; } final String bucketName = s3Path.bucketName(); final S3FileSystem fs = new S3FileSystem(bucketName); final String prefix = pathString; long timeOut = TIMEOUT_TIME_LENGTH_1; final TimeUnit unit = MINUTES; try { final Iterator filteredDirectoryContents = s3Client.listObjectsV2(req -> req .bucket(bucketName) .prefix(prefix)) .get(timeOut, unit) .contents() .stream() .map(s3Object -> truncateByPrefix(fs, prefix, s3Object)) .filter(path -> { try { return filter.accept(path); } catch (IOException e) { e.printStackTrace(); return false; } }) .iterator(); return new DirectoryStream() { final Iterator iterator = filteredDirectoryContents; @Override @SuppressWarnings("unchecked") public Iterator iterator() { return (Iterator) iterator; } @Override public void close() { // nothing to close } }; } catch (TimeoutException e) { throw logAndGenerateExceptionOnTimeOut(logger, "newDirectoryStream", timeOut, unit); } } /** * truncate objects whose key after the prefix contains a "/" to the first "/" after the prefix */ private S3Path truncateByPrefix(final S3FileSystem fs, final String prefix, final S3Object object) { if (object.key().indexOf(prefix) != 0 || object.key().equals(prefix)) { return S3Path.getPath(fs, object); } int indexOfNextSeparator = object.key().indexOf(S3Path.PATH_SEPARATOR, prefix.length()); String truncated = indexOfNextSeparator == -1 ? object.key() : object.key().substring(0, indexOfNextSeparator); return fs.getPath(truncated); } /** * Creates a new directory. This method works in exactly the manner * specified by the {@link Files#createDirectory} method. * * @param dir the directory to create * @param attrs an optional list of file attributes to set atomically when * creating the directory */ @Override public void createDirectory(Path dir, FileAttribute... attrs) throws IOException { try { this.createDirectory(null, dir, attrs); } catch (ExecutionException e) { throw new IOException(e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } protected void createDirectory(S3AsyncClient s3Client, Path dir, FileAttribute... attrs) throws ExecutionException, InterruptedException { S3Path s3Path = (S3Path) dir; final String bucketName = s3Path.bucketName(); if (s3Client == null) { s3Client = S3ClientStore.getInstance().getAsyncClientForBucketName(s3Path.bucketName()); } String directoryKey = s3Path.toRealPath(NOFOLLOW_LINKS).getKey(); if (!directoryKey.endsWith(S3Path.PATH_SEPARATOR) && !directoryKey.isEmpty()) { directoryKey = directoryKey + S3Path.PATH_SEPARATOR; } long timeOut = TIMEOUT_TIME_LENGTH_1; final TimeUnit unit = MINUTES; try { s3Client.putObject(PutObjectRequest.builder() .bucket(bucketName) .key(directoryKey) .build(), AsyncRequestBody.empty()) .get(timeOut, unit); } catch (TimeoutException e) { throw logAndGenerateExceptionOnTimeOut(logger, "createDirectory", timeOut, unit); } } /** * Deletes a file. This method works in exactly the manner specified by the * {@link Files#delete} method. * * @param path the path to the file to delete */ @Override public void delete(Path path) throws IOException { try { this.delete(null, path); } catch (ExecutionException e) { throw new IOException(e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } protected void delete(S3AsyncClient s3Client, Path path) throws ExecutionException, InterruptedException { S3Path s3Path = (S3Path) path; if (s3Client == null) { s3Client = S3ClientStore.getInstance().getAsyncClientForBucketName(s3Path.bucketName()); } String prefix = s3Path.toRealPath(NOFOLLOW_LINKS).getKey(); final String bucketName = s3Path.bucketName(); long timeOut = TIMEOUT_TIME_LENGTH_1; final TimeUnit unit = MINUTES; try { List> keys = getContainedObjectBatches(s3Client, bucketName, prefix, timeOut, unit); for (List keyList : keys) { s3Client.deleteObjects(DeleteObjectsRequest.builder() .bucket(bucketName) .delete(Delete.builder() .objects(keyList) .build()) .build()) .get(timeOut, unit); } } catch (TimeoutException e) { throw logAndGenerateExceptionOnTimeOut(logger, "delete", timeOut, unit); } } private static List> getContainedObjectBatches(S3AsyncClient s3Client, String bucketName, String prefix, long timeOut, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { String continuationToken = null; boolean hasMoreItems = true; List> keys = new ArrayList<>(); while (hasMoreItems) { String finalContinuationToken = continuationToken; ListObjectsV2Response response = s3Client.listObjectsV2(req -> req .bucket(bucketName) .prefix(prefix) .continuationToken(finalContinuationToken)) .get(timeOut, unit); List objects = response.contents() .stream() .filter(s3Object -> s3Object.key().equals(prefix) || s3Object.key().startsWith(prefix + S3Path.PATH_SEPARATOR)) .map(s3Object -> ObjectIdentifier.builder().key(s3Object.key()).build()) .collect(Collectors.toList()); if (!objects.isEmpty()) { keys.add(objects); } hasMoreItems = response.isTruncated(); continuationToken = response.nextContinuationToken(); } return keys; } /** * 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. * * @param source the path to the file to copy * @param target the path to the target file * @param options options specifying how the copy should be done */ @Override public void copy(Path source, Path target, CopyOption... options) throws IOException { try { this.copy(null, source, target, options); } catch (ExecutionException e) { throw new IOException(e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } protected void copy(S3AsyncClient s3Client, Path source, Path target, CopyOption... options) throws ExecutionException, InterruptedException, FileAlreadyExistsException { // If both paths point to the same object, this is a no-op if (source.equals(target)) { return; } List copyOptions = Arrays.asList(options); S3Path s3SourcePath = (S3Path) source; S3Path s3TargetPath = (S3Path) target; if (s3Client == null) { s3Client = S3ClientStore.getInstance().getAsyncClientForBucketName(s3SourcePath.bucketName()); } String prefix = s3SourcePath.toRealPath(NOFOLLOW_LINKS).getKey(); final String bucketName = s3SourcePath.bucketName(); long timeOut = TIMEOUT_TIME_LENGTH_1; final TimeUnit unit = MINUTES; try { List> keys = getContainedObjectBatches(s3Client, bucketName, prefix, timeOut, unit); for (List keyList : keys) { for (ObjectIdentifier objectIdentifier : keyList) { S3Path resolvedS3TargetPath = s3TargetPath.resolve(objectIdentifier.key().replaceFirst(prefix + S3Path.PATH_SEPARATOR, "")); if (!copyOptions.contains(StandardCopyOption.REPLACE_EXISTING) && exists(s3Client, resolvedS3TargetPath)) { throw new FileAlreadyExistsException("File already exists at the target key"); } try (S3TransferManager s3TransferManager = S3TransferManager.builder().s3Client(s3Client).build()) { s3TransferManager.copy(CopyRequest.builder() .copyObjectRequest(CopyObjectRequest.builder() .checksumAlgorithm(ChecksumAlgorithm.SHA256) .sourceBucket(bucketName) .sourceKey(objectIdentifier.key()) .destinationBucket(resolvedS3TargetPath.bucketName()) .destinationKey(resolvedS3TargetPath.getKey()) .build()) .build()).completionFuture().join(); } } } } catch (TimeoutException e) { throw logAndGenerateExceptionOnTimeOut(logger, "copy", timeOut, unit); } } protected boolean exists(S3AsyncClient s3Client, S3Path path) throws InterruptedException, TimeoutException { try { s3Client.headObject(HeadObjectRequest.builder().bucket(path.bucketName()).key(path.getKey()).build()) .get(TIMEOUT_TIME_LENGTH_1, MINUTES); return true; } catch (ExecutionException | NoSuchKeyException e) { logger.debug("Could not retrieve object head information", e); return false; } } /** * Move or rename a file to a target file. This method works in exactly the * manner specified by the {@link Files#move} method except that both the * source and target paths must be associated with this provider. * * @param source the path to the file to move * @param target the path to the target file * @param options options specifying how the move should be done */ @Override public void move(Path source, Path target, CopyOption... options) throws IOException { try { this.move(null, source, target, options); } catch (ExecutionException e) { throw new IOException(e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } protected void move(S3AsyncClient s3AsyncClient, Path source, Path target, CopyOption... options) throws ExecutionException, InterruptedException, FileAlreadyExistsException { this.copy(s3AsyncClient, source, target, options); this.delete(s3AsyncClient, source); } /** * Tests if two paths locate the same file. This method works in exactly the * manner specified by the {@link Files#isSameFile} method. * * @param path one path to the file * @param path2 the other path * @return {@code true} if, and only if, the two paths locate the same file * @throws IOException if an I/O error occurs * @throws SecurityException In the case of the default provider, and a security manager is * installed, the {@link SecurityManager#checkRead(String) checkRead} * method is invoked to check read access to both files. */ @Override public boolean isSameFile(Path path, Path path2) throws IOException { return path.toRealPath(NOFOLLOW_LINKS).equals(path2.toRealPath(NOFOLLOW_LINKS)); } /** * There are no hidden files in S3 * * @param path the path to the file to test * @return {@code false} always */ @Override public boolean isHidden(Path path) { return false; } /** * S3 buckets don't have partitions or volumes so there are no file stores * * @param path the path to the file * @return {@code null} always */ @Override public FileStore getFileStore(Path path) { return null; } /** * Checks the existence, and optionally the accessibility, of a file. * *

This method may be used by the {@link Files#isReadable isReadable}, * {@link Files#isWritable isWritable} and {@link Files#isExecutable * isExecutable} methods to check the accessibility of a file. * *

This method checks the existence of a file and that this Java virtual * machine has appropriate privileges that would allow it to access the file * according to all the access modes specified in the {@code modes} parameter * as follows: *
*

* * * * * * * * * * * * * * * * * * *
Access Modes
Value Description
{@link AccessMode#READ READ} Checks that the file exists and that the Java virtual machine has * permission to read the file.
{@link AccessMode#WRITE WRITE} Checks that the file exists and that the Java virtual machine has * permission to write to the file,
{@link AccessMode#EXECUTE EXECUTE} Checks that the file exists and that the Java virtual machine has * permission to {@link Runtime#exec execute} the file. The semantics * may differ when checking access to a directory. For example, on UNIX * systems, checking for {@code EXECUTE} access checks that the Java * virtual machine has permission to search the directory in order to * access file or subdirectories.
* *

If the {@code modes} parameter is of length zero, then the existence * of the file is checked. * *

This method follows symbolic links if the file referenced by this * object is a symbolic link. Depending on the implementation, this method * may require reading file permissions, access control lists, or other * file attributes in order to check the effective access to the file. To * determine the effective access to a file may require access to several * attributes and so in some implementations this method may not be atomic * with respect to other file system operations. * * @param path the path to the file to check * @param modes The access modes to check; may have zero elements * @throws UnsupportedOperationException an implementation is required to support checking for * {@code READ}, {@code WRITE}, and {@code EXECUTE} access. This * exception is specified to allow for the {@code Access} enum to * be extended in future releases. * @throws NoSuchFileException if a file does not exist (optional specific exception) * @throws AccessDeniedException the requested access would be denied or the access cannot be * determined because the Java virtual machine has insufficient * privileges or other reasons. (optional specific exception) * @throws IOException if an I/O error occurs * @throws SecurityException In the case of the default provider, and a security manager is * installed, the {@link SecurityManager#checkRead(String) checkRead} * is invoked when checking read access to the file or only the * existence of the file, the {@link SecurityManager#checkWrite(String) * checkWrite} is invoked when checking write access to the file, * and {@link SecurityManager#checkExec(String) checkExec} is invoked * when checking execute access. */ @Override public void checkAccess(Path path, AccessMode... modes) throws IOException { try { this.checkAccess(null, path, modes); } catch (ExecutionException e) { throw new IOException(e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } /** * Composable and testable version of {@code checkAccess} that uses the provided client to check access */ protected void checkAccess(S3AsyncClient s3Client, Path path, AccessMode... modes) throws IOException, ExecutionException, InterruptedException { assert path instanceof S3Path; S3Path s3Path = (S3Path) path.toRealPath(NOFOLLOW_LINKS); final String bucketName = s3Path.getFileSystem().bucketName(); if (s3Client == null) { s3Client = S3ClientStore.getInstance().getAsyncClientForBucketName(bucketName); } final CompletableFuture response; if (s3Path.equals(s3Path.getRoot())) { response = s3Client.headBucket(request -> request.bucket(bucketName)); } else { response = s3Client.headObject(req -> req.bucket(bucketName).key(s3Path.getKey())); } long timeOut = TimeOutUtils.TIMEOUT_TIME_LENGTH_1; TimeUnit unit = MINUTES; try { SdkHttpResponse httpResponse = response.get(timeOut, unit).sdkHttpResponse(); if (httpResponse.isSuccessful()) return; if (httpResponse.statusCode() == FORBIDDEN) throw new AccessDeniedException(s3Path.toString()); if (httpResponse.statusCode() == NOT_FOUND) throw new NoSuchFileException(s3Path.toString()); throw new IOException(String.format("exception occurred while checking access, response code was '%d'", httpResponse.statusCode())); } catch (TimeoutException e) { throw logAndGenerateExceptionOnTimeOut(logger, "checkAccess", timeOut, unit); } } /** * Returns a file attribute view of a given type. This method works in * exactly the manner specified by the {@link Files#getFileAttributeView} * method. * * @param path the path to the file * @param type the {@code Class} object corresponding to the file attribute view. * Must be {@code BasicFileAttributeView.class} or {@code S3FileAttributeView.class} * @param options ignored as there are no links in S3 * @return a file attribute view of the specified type, or {@code null} if * the attribute view type is not available */ @Override public V getFileAttributeView(Path path, Class type, LinkOption... options) { Objects.requireNonNull(path, "cannot obtain attributes for a null path"); Objects.requireNonNull(type, "the type of attribute view required cannot be null"); if (!(path instanceof S3Path)) throw new IllegalArgumentException("path must be an S3 Path"); S3Path s3Path = (S3Path) path; if (type.equals(BasicFileAttributeView.class) || type.equals(S3FileAttributeView.class)) { @SuppressWarnings("unchecked") final V v = (V) new S3FileAttributeView(s3Path); return v; } else { throw new IllegalArgumentException("type must be BasicFileAttributeView.class or S3FileAttributeView.class"); } } /** * 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. * * @param path the path to the file * @param type the {@code Class} of the file attributes required * to read. Supported types are {@code BasicFileAttributes} and {@code S3FileAttributes} * @param options options indicating how symbolic links are handled * @return the file attributes or {@code null} if {@code path} is inferred to be a directory. */ @Override public A readAttributes(Path path, Class type, LinkOption... options) { return this.readAttributes(null, path, type, options); } protected A readAttributes(S3AsyncClient s3AsyncClient, Path path, Class type, LinkOption... options) { Objects.requireNonNull(path); Objects.requireNonNull(type); if (!(path instanceof S3Path)) throw new IllegalArgumentException("path must be an S3Path instance"); S3Path s3Path = (S3Path) path; if (s3Path.isDirectory()) return null; if (type.equals(BasicFileAttributes.class) || type.equals(S3BasicFileAttributes.class)) { if (s3AsyncClient == null) { s3AsyncClient = S3ClientStore.getInstance().getAsyncClientForBucketName(s3Path.bucketName()); } @SuppressWarnings("unchecked") A a = (A) new S3BasicFileAttributes(s3Path, s3AsyncClient); return a; } else { throw new UnsupportedOperationException("cannot read attributes of type: " + type); } } /** * Reads a set of file attributes as a bulk operation. Largely equivalent to {@code readAttributes(Path path, Class type, LinkOption... options)} * where the returned object is a map of method names (attributes) to values, filtered on the comma separated {@code attributes}. * * @param path the path to the file * @param attributes the comma separated attributes to read. May be prefixed with "s3:" * @param options ignored, S3 has no links * @return a map of the attributes returned; may be empty. The map's keys * are the attribute names, its values are the attribute values. Returns an empty map if {@code attributes} is empty, * or if {@code path} is inferred to be a directory. * @throws UnsupportedOperationException if the attribute view is not available * @throws IllegalArgumentException if no attributes are specified or an unrecognized attributes is * specified * @throws SecurityException In the case of the default provider, and a security manager is * installed, its {@link SecurityManager#checkRead(String) checkRead} * method denies read access to the file. If this method is invoked * to read security sensitive attributes then the security manager * may be invoked to check for additional permissions. */ @Override public Map readAttributes(Path path, String attributes, LinkOption... options) { return this.readAttributes(null, path, attributes, options); } protected Map readAttributes(S3AsyncClient client, Path path, String attributes, LinkOption... options) { Objects.requireNonNull(path); Objects.requireNonNull(attributes); S3Path s3Path = (S3Path) path; if (client == null) { client = S3ClientStore.getInstance().getAsyncClientForBucketName(s3Path.bucketName()); } if (s3Path.isDirectory() || attributes.trim().isEmpty()) return Collections.emptyMap(); if (attributes.equals("*") || attributes.equals("s3")) return new S3BasicFileAttributes(s3Path, client).asMap(); final Set attrSet = Arrays.stream(attributes.split(",")) .map(attr -> attr.replaceAll("^s3:", "")) .collect(Collectors.toSet()); return readAttributes(client, path, S3BasicFileAttributes.class, options) .asMap(attrSet::contains); } /** * File attributes of S3 objects cannot be set other than by creating a new object * * @throws UnsupportedOperationException always */ @Override public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws UnsupportedOperationException { throw new UnsupportedOperationException("s3 file attributes cannot be modified by this class"); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy