-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Nio read attr #12621
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Nio read attr #12621
Changes from all commits
5876790
d23f348
6e5c44d
2de8168
e2c8ef4
3bb0ded
3e5b74b
c782de0
1abff9c
45d331e
f248026
0903e53
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| package com.azure.storage.blob.nio; | ||
|
|
||
| import java.io.IOException; | ||
| import java.nio.file.Path; | ||
| import java.nio.file.attribute.BasicFileAttributeView; | ||
| import java.nio.file.attribute.FileTime; | ||
|
|
||
| /** | ||
| * Provides support for basic file attributes. | ||
| * | ||
| * The operations supported by this view and the attributes it reads are a strict subset of | ||
| * {@link AzureBlobFileAttributeView} and has the same network behavior. Therefore, while this type is offered for | ||
| * compliance with the NIO spec, {@link AzureBlobFileAttributeView} is generally preferred. | ||
| * | ||
| * {@link #setTimes(FileTime, FileTime, FileTime)} is not supported. | ||
| * | ||
| * {@inheritDoc} | ||
| */ | ||
| public final class AzureBasicFileAttributeView implements BasicFileAttributeView { | ||
|
|
||
| private final Path path; | ||
|
|
||
| AzureBasicFileAttributeView(Path path) { | ||
| this.path = path; | ||
| } | ||
|
|
||
| /** | ||
| * Returns {@code "azureBasic"} | ||
| * {@inheritDoc} | ||
| */ | ||
| @Override | ||
| public String name() { | ||
| return "azureBasic"; | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| @Override | ||
| public AzureBasicFileAttributes readAttributes() throws IOException { | ||
| return new AzureBasicFileAttributes(path); | ||
| } | ||
|
|
||
| /** | ||
| * Unsupported. | ||
| * | ||
| * @throws UnsupportedOperationException Operation not supported. | ||
| * {@inheritDoc} | ||
| */ | ||
| @Override | ||
| public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException { | ||
| throw new UnsupportedOperationException(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| package com.azure.storage.blob.nio; | ||
|
|
||
| import com.azure.core.util.logging.ClientLogger; | ||
| import com.azure.storage.blob.models.BlobProperties; | ||
| import com.azure.storage.blob.models.BlobStorageException; | ||
|
|
||
| import java.io.IOException; | ||
| import java.nio.file.Path; | ||
| import java.nio.file.attribute.BasicFileAttributes; | ||
| import java.nio.file.attribute.FileAttribute; | ||
| import java.nio.file.attribute.FileTime; | ||
| import java.util.Collections; | ||
| import java.util.HashSet; | ||
| import java.util.Set; | ||
|
|
||
| /** | ||
| * Provides support for basic file attributes. | ||
| * <p> | ||
| * The properties available on this type are a strict subset of {@link AzureBlobFileAttributes}, and the two types have | ||
| * the same network behavior. Therefore, while this type is offered for compliance with the NIO spec, | ||
| * {@link AzureBlobFileAttributes} is generally preferred. | ||
| * <p> | ||
| * Some attributes are not supported. Refer to the javadocs on each method for more information. | ||
| * {@inheritDoc} | ||
| */ | ||
| public class AzureBasicFileAttributes implements BasicFileAttributes { | ||
| private final ClientLogger logger = new ClientLogger(AzureBasicFileAttributes.class); | ||
|
|
||
| // For verifying parameters on FileSystemProvider.readAttributes | ||
| static final Set<String> ATTRIBUTE_STRINGS; | ||
| static { | ||
| Set<String> set = new HashSet<>(); | ||
| set.add("lastModifiedTime"); | ||
| set.add("isRegularFile"); | ||
| set.add("isDirectory"); | ||
| set.add("isSymbolicLink"); | ||
| set.add("isOther"); | ||
| set.add("size"); | ||
| set.add("creationTime"); | ||
| ATTRIBUTE_STRINGS = Collections.unmodifiableSet(set); | ||
| } | ||
|
|
||
| private final BlobProperties properties; | ||
|
|
||
| /* | ||
| There are some work-arounds we could do to try to accommodate virtual directories such as making a checkDirStatus | ||
| call before or after getProperties to throw an appropriate error or adding an isVirtualDirectory method. However, | ||
| the former wastes network time only to throw a slightly more specific error when we will throw on 404 anyway. The | ||
| latter introduces virtual directories into the actual code path/api surface. While we are clear in our docs about | ||
| the possible pitfalls of virtual directories, and customers should be aware of it, they shouldn't have to code | ||
| against it. Therefore, we fall back to documenting that reading attributes on a virtual directory will throw. | ||
| */ | ||
| AzureBasicFileAttributes(Path path) throws IOException { | ||
| try { | ||
| this.properties = new AzureResource(path).getBlobClient().getProperties(); | ||
| } catch (BlobStorageException e) { | ||
| throw LoggingUtility.logError(logger, new IOException(e)); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| @Override | ||
| public FileTime lastModifiedTime() { | ||
| return FileTime.from(properties.getLastModified().toInstant()); | ||
| } | ||
|
|
||
| /** | ||
| * Unsupported. | ||
| * @throws UnsupportedOperationException Operation not supported. | ||
| * {@inheritDoc} | ||
| */ | ||
| @Override | ||
| public FileTime lastAccessTime() { | ||
| throw new UnsupportedOperationException(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might want to add @throws to javadocs for unsupported apis. |
||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| @Override | ||
| public FileTime creationTime() { | ||
| return FileTime.from(properties.getCreationTime().toInstant()); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| @Override | ||
| public boolean isRegularFile() { | ||
| return !this.properties.getMetadata().getOrDefault(AzureResource.DIR_METADATA_MARKER, "false").equals("true"); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| * <p> | ||
| * Will only return true if the directory is a concrete directory. See | ||
| * {@link AzureFileSystemProvider#createDirectory(Path, FileAttribute[])} for more information on virtual and | ||
| * concrete directories. | ||
| */ | ||
| @Override | ||
| public boolean isDirectory() { | ||
| return !this.isRegularFile(); | ||
| } | ||
|
|
||
| /** | ||
| * @return false. Symbolic links are not supported. | ||
| */ | ||
| @Override | ||
| public boolean isSymbolicLink() { | ||
| return false; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this throw
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure. In some sense it's just always false, right? But maybe it makes it more clear that sym links aren't supported if I throw?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think returning always |
||
| } | ||
|
|
||
| /** | ||
| * @return false | ||
| */ | ||
| @Override | ||
| public boolean isOther() { | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| */ | ||
| @Override | ||
| public long size() { | ||
| return properties.getBlobSize(); | ||
| } | ||
|
|
||
| /** | ||
| * Unsupported. | ||
| * @throws UnsupportedOperationException Operation not supported. | ||
| * {@inheritDoc} | ||
| */ | ||
| @Override | ||
| public Object fileKey() { | ||
| throw new UnsupportedOperationException(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| package com.azure.storage.blob.nio; | ||
|
|
||
| import com.azure.core.util.logging.ClientLogger; | ||
| import com.azure.storage.blob.models.AccessTier; | ||
| import com.azure.storage.blob.models.BlobHttpHeaders; | ||
| import com.azure.storage.blob.models.BlobStorageException; | ||
| import com.azure.storage.blob.specialized.BlobClientBase; | ||
|
|
||
| import java.io.IOException; | ||
| import java.io.UncheckedIOException; | ||
| import java.nio.file.Path; | ||
| import java.nio.file.attribute.BasicFileAttributeView; | ||
| import java.nio.file.attribute.FileTime; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| import java.util.function.Consumer; | ||
|
|
||
| /** | ||
| * A file attribute view that provides a view of attributes specific to files stored as blobs in Azure Storage. | ||
| * <p> | ||
| * All attributes are retrieved from the file system as a bulk operation. | ||
| * <p> | ||
| * {@link #setTimes(FileTime, FileTime, FileTime)} is not supported. | ||
| */ | ||
| public final class AzureBlobFileAttributeView implements BasicFileAttributeView { | ||
| private final ClientLogger logger = new ClientLogger(AzureBlobFileAttributeView.class); | ||
|
|
||
| static final String ATTR_CONSUMER_ERROR = "Exception thrown by attribute consumer"; | ||
|
|
||
| private final Path path; | ||
|
|
||
| AzureBlobFileAttributeView(Path path) { | ||
| this.path = path; | ||
| } | ||
|
|
||
| @SuppressWarnings("unchecked") | ||
| static Map<String, Consumer<Object>> setAttributeConsumers(AzureBlobFileAttributeView view) { | ||
| Map<String, Consumer<Object>> map = new HashMap<>(); | ||
| map.put("blobHttpHeaders", obj -> { | ||
| try { | ||
| view.setBlobHttpHeaders((BlobHttpHeaders) obj); | ||
| } catch (IOException e) { | ||
| throw LoggingUtility.logError(view.logger, new UncheckedIOException(ATTR_CONSUMER_ERROR, e)); | ||
| } | ||
| }); | ||
| map.put("metadata", obj -> { | ||
| try { | ||
| Map<String, String> m = (Map<String, String>) obj; | ||
| if (m == null) { | ||
| throw LoggingUtility.logError(view.logger, new ClassCastException()); | ||
| } | ||
| view.setMetadata(m); | ||
| } catch (IOException e) { | ||
| throw LoggingUtility.logError(view.logger, new UncheckedIOException(ATTR_CONSUMER_ERROR, e)); | ||
| } | ||
| }); | ||
| map.put("tier", obj -> { | ||
| try { | ||
| view.setTier((AccessTier) obj); | ||
| } catch (IOException e) { | ||
| throw LoggingUtility.logError(view.logger, new UncheckedIOException(ATTR_CONSUMER_ERROR, e)); | ||
| } | ||
| }); | ||
|
|
||
| return map; | ||
| } | ||
|
|
||
| /** | ||
| * Returns "azureBlob". | ||
| * {@inheritDoc} | ||
| */ | ||
| @Override | ||
| public String name() { | ||
| return "azureBlob"; | ||
| } | ||
|
|
||
| /** | ||
| * Reads the file attributes as a bulk operation. | ||
| * | ||
| * Gets a fresh copy every time it is called. | ||
| * @return {@link AzureBlobFileAttributes} | ||
| * @throws IOException if an IOException occurs. | ||
| */ | ||
| @Override | ||
| public AzureBlobFileAttributes readAttributes() throws IOException { | ||
| return new AzureBlobFileAttributes(path); | ||
| } | ||
|
|
||
| /** | ||
| * Sets the {@link BlobHttpHeaders} as an atomic operation. | ||
| * <p> | ||
| * See {@link BlobClientBase#setHttpHeaders(BlobHttpHeaders)} for more information. | ||
| * @param headers {@link BlobHttpHeaders} | ||
| * @throws IOException if an IOException occurs. | ||
| */ | ||
| public void setBlobHttpHeaders(BlobHttpHeaders headers) throws IOException { | ||
| try { | ||
| new AzureResource(this.path).getBlobClient().setHttpHeaders(headers); | ||
| } catch (BlobStorageException e) { | ||
| throw LoggingUtility.logError(logger, new IOException(e)); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Sets the metadata as an atomic operation. | ||
| * <p> | ||
| * See {@link BlobClientBase#setMetadata(Map)} for more information. | ||
| * @param metadata The metadata to associate with the blob | ||
| * @throws IOException if an IOException occurs. | ||
| */ | ||
| public void setMetadata(Map<String, String> metadata) throws IOException { | ||
| try { | ||
| new AzureResource(this.path).getBlobClient().setMetadata(metadata); | ||
| } catch (BlobStorageException e) { | ||
| throw LoggingUtility.logError(logger, new IOException(e)); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Sets the {@link AccessTier} on the file. | ||
| * | ||
| * See {@link BlobClientBase#setAccessTier(AccessTier)} for more information. | ||
| * @param tier {@link AccessTier} | ||
| * @throws IOException if an IOException occurs. | ||
| */ | ||
| public void setTier(AccessTier tier) throws IOException { | ||
| try { | ||
| new AzureResource(this.path).getBlobClient().setAccessTier(tier); | ||
| } catch (BlobStorageException e) { | ||
| throw LoggingUtility.logError(logger, new IOException(e)); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Unsupported. | ||
| * | ||
| * @throws UnsupportedOperationException Operation not supported. | ||
| * {@inheritDoc} | ||
| */ | ||
| @Override | ||
| public void setTimes(FileTime fileTime, FileTime fileTime1, FileTime fileTime2) throws IOException { | ||
| throw new UnsupportedOperationException(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if properties change while someone is holding reference to this object? I.e. lastModifiedTime shifts between calling this constructor and lastModifiedTime()?
Could you check what happens if you use local file system?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the attributes always return the same value once you've read them, and if you want an updated value, you need to make another call to read attributes, which is what we do here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cool, thanks for checking.