Skip to content

Commit ad5e96b

Browse files
authored
feat: FileSystemProvider::readAttributes for basic and gcs views (#1066)
1 parent bca6ba1 commit ad5e96b

2 files changed

Lines changed: 187 additions & 2 deletions

File tree

java-storage-nio/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProvider.java

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,15 @@
6464
import java.nio.file.attribute.FileAttributeView;
6565
import java.nio.file.spi.FileSystemProvider;
6666
import java.util.ArrayList;
67+
import java.util.Arrays;
6768
import java.util.Collections;
6869
import java.util.HashMap;
6970
import java.util.Iterator;
7071
import java.util.List;
7172
import java.util.Map;
7273
import java.util.Objects;
7374
import java.util.Set;
75+
import java.util.TreeMap;
7476
import javax.annotation.Nullable;
7577
import javax.annotation.concurrent.ThreadSafe;
7678
import javax.inject.Singleton;
@@ -839,11 +841,93 @@ whose name ends in slash (and these files aren't always zero-size).
839841
}
840842

841843
@Override
842-
public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) {
844+
public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options)
845+
throws IOException {
843846
// TODO(#811): Java 7 NIO defines at least eleven string attributes we'd want to support
844847
// (eg. BasicFileAttributeView and PosixFileAttributeView), so rather than a partial
845848
// implementation we rely on the other overload for now.
846-
throw new UnsupportedOperationException();
849+
850+
// Partial implementation for a few commonly used ones: basic, gcs
851+
String[] split = attributes.split(":", 2);
852+
if (split.length != 2) {
853+
throw new UnsupportedOperationException();
854+
}
855+
String view = split[0];
856+
List<String> attributeNames = Arrays.asList(split[1].split(","));
857+
boolean allAttributes = attributeNames.size() == 1 && attributeNames.get(0).equals("*");
858+
859+
BasicFileAttributes fileAttributes;
860+
861+
Map<String, Object> results = new TreeMap<>();
862+
switch (view) {
863+
case "gcs":
864+
fileAttributes = readAttributes(path, CloudStorageFileAttributes.class, options);
865+
break;
866+
case "basic":
867+
fileAttributes = readAttributes(path, BasicFileAttributes.class, options);
868+
break;
869+
default:
870+
throw new UnsupportedOperationException();
871+
}
872+
873+
if (fileAttributes == null) {
874+
throw new UnsupportedOperationException();
875+
}
876+
877+
// BasicFileAttributes
878+
if (allAttributes || attributeNames.contains("lastModifiedTime")) {
879+
results.put("lastModifiedTime", fileAttributes.lastModifiedTime());
880+
}
881+
if (allAttributes || attributeNames.contains("lastAccessTime")) {
882+
results.put("lastAccessTime", fileAttributes.lastAccessTime());
883+
}
884+
if (allAttributes || attributeNames.contains("creationTime")) {
885+
results.put("creationTime", fileAttributes.creationTime());
886+
}
887+
if (allAttributes || attributeNames.contains("isRegularFile")) {
888+
results.put("isRegularFile", fileAttributes.isRegularFile());
889+
}
890+
if (allAttributes || attributeNames.contains("isDirectory")) {
891+
results.put("isDirectory", fileAttributes.isDirectory());
892+
}
893+
if (allAttributes || attributeNames.contains("isSymbolicLink")) {
894+
results.put("isSymbolicLink", fileAttributes.isSymbolicLink());
895+
}
896+
if (allAttributes || attributeNames.contains("isOther")) {
897+
results.put("isOther", fileAttributes.isOther());
898+
}
899+
if (allAttributes || attributeNames.contains("size")) {
900+
results.put("size", fileAttributes.size());
901+
}
902+
903+
// CloudStorageFileAttributes
904+
if (fileAttributes instanceof CloudStorageFileAttributes) {
905+
CloudStorageFileAttributes cloudStorageFileAttributes =
906+
(CloudStorageFileAttributes) fileAttributes;
907+
if (allAttributes || attributeNames.contains("etag")) {
908+
results.put("etag", cloudStorageFileAttributes.etag());
909+
}
910+
if (allAttributes || attributeNames.contains("mimeType")) {
911+
results.put("mimeType", cloudStorageFileAttributes.mimeType());
912+
}
913+
if (allAttributes || attributeNames.contains("acl")) {
914+
results.put("acl", cloudStorageFileAttributes.acl());
915+
}
916+
if (allAttributes || attributeNames.contains("cacheControl")) {
917+
results.put("cacheControl", cloudStorageFileAttributes.cacheControl());
918+
}
919+
if (allAttributes || attributeNames.contains("contentEncoding")) {
920+
results.put("contentEncoding", cloudStorageFileAttributes.contentEncoding());
921+
}
922+
if (allAttributes || attributeNames.contains("contentDisposition")) {
923+
results.put("contentDisposition", cloudStorageFileAttributes.contentDisposition());
924+
}
925+
if (allAttributes || attributeNames.contains("userMetadata")) {
926+
results.put("userMetadata", cloudStorageFileAttributes.userMetadata());
927+
}
928+
}
929+
930+
return results;
847931
}
848932

849933
@Override

java-storage-nio/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProviderTest.java

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.cloud.storage.contrib.nio;
1818

19+
import static com.google.cloud.storage.Acl.Role.OWNER;
1920
import static com.google.cloud.storage.contrib.nio.CloudStorageFileSystem.forBucket;
2021
import static com.google.common.truth.Truth.assertThat;
2122
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -26,10 +27,17 @@
2627
import static java.nio.file.StandardOpenOption.CREATE_NEW;
2728
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
2829
import static java.nio.file.StandardOpenOption.WRITE;
30+
import static org.junit.Assert.assertEquals;
2931
import static org.junit.Assert.assertTrue;
32+
import static org.mockito.Mockito.mock;
33+
import static org.mockito.Mockito.spy;
34+
import static org.mockito.Mockito.when;
3035

36+
import com.google.cloud.storage.Acl;
37+
import com.google.cloud.storage.Acl.User;
3138
import com.google.cloud.storage.contrib.nio.testing.LocalStorageHelper;
3239
import com.google.cloud.testing.junit4.MultipleAttemptsRule;
40+
import com.google.common.base.Optional;
3341
import com.google.common.collect.ImmutableList;
3442
import com.google.common.testing.NullPointerTester;
3543
import java.io.IOException;
@@ -49,10 +57,13 @@
4957
import java.nio.file.OpenOption;
5058
import java.nio.file.Path;
5159
import java.nio.file.Paths;
60+
import java.nio.file.attribute.BasicFileAttributes;
61+
import java.nio.file.attribute.FileTime;
5262
import java.util.ArrayList;
5363
import java.util.HashMap;
5464
import java.util.List;
5565
import java.util.Map;
66+
import java.util.TreeMap;
5667
import org.junit.After;
5768
import org.junit.Assert;
5869
import org.junit.Before;
@@ -817,6 +828,96 @@ public void getUserAgentStartsWithCorrectToken() {
817828
.startsWith("gcloud-java-nio/");
818829
}
819830

831+
@Test
832+
public void testReadAttributes() throws IOException {
833+
CloudStorageFileSystem fileSystem = forBucket("dummy");
834+
CloudStorageFileSystemProvider fileSystemProvider = spy(fileSystem.provider());
835+
836+
BasicFileAttributes attributesBasic = mock(BasicFileAttributes.class);
837+
// BasicFileAttributes
838+
when(attributesBasic.creationTime()).thenReturn(FileTime.fromMillis(1L));
839+
when(attributesBasic.lastModifiedTime()).thenReturn(FileTime.fromMillis(2L));
840+
when(attributesBasic.lastAccessTime()).thenReturn(FileTime.fromMillis(3L));
841+
when(attributesBasic.isRegularFile()).thenReturn(true);
842+
when(attributesBasic.isDirectory()).thenReturn(true);
843+
when(attributesBasic.isSymbolicLink()).thenReturn(true);
844+
when(attributesBasic.isOther()).thenReturn(true);
845+
when(attributesBasic.size()).thenReturn(42L);
846+
847+
CloudStorageFileAttributes attributesGcs = mock(CloudStorageFileAttributes.class);
848+
// BasicFileAttributes
849+
when(attributesGcs.creationTime()).thenReturn(FileTime.fromMillis(1L));
850+
when(attributesGcs.lastModifiedTime()).thenReturn(FileTime.fromMillis(2L));
851+
when(attributesGcs.lastAccessTime()).thenReturn(FileTime.fromMillis(3L));
852+
when(attributesGcs.isRegularFile()).thenReturn(true);
853+
when(attributesGcs.isDirectory()).thenReturn(true);
854+
when(attributesGcs.isSymbolicLink()).thenReturn(true);
855+
when(attributesGcs.isOther()).thenReturn(true);
856+
when(attributesGcs.size()).thenReturn(42L);
857+
858+
List<Acl> acls = ImmutableList.of(Acl.newBuilder(new User("Foo"), OWNER).build());
859+
860+
// CloudStorageFileAttributes
861+
when(attributesGcs.etag()).thenReturn(Optional.of("TheEtag"));
862+
when(attributesGcs.mimeType()).thenReturn(Optional.of("TheMimeType"));
863+
when(attributesGcs.acl()).thenReturn(Optional.of(acls));
864+
when(attributesGcs.cacheControl()).thenReturn(Optional.of("TheCacheControl"));
865+
when(attributesGcs.contentEncoding()).thenReturn(Optional.of("TheContentEncoding"));
866+
when(attributesGcs.contentDisposition()).thenReturn(Optional.of("TheContentDisposition"));
867+
when(attributesGcs.userMetadata()).thenReturn(new TreeMap<>());
868+
869+
CloudStoragePath path1 = CloudStoragePath.getPath(fileSystem, "/");
870+
when(fileSystemProvider.readAttributes(path1, BasicFileAttributes.class))
871+
.thenReturn(attributesBasic);
872+
when(fileSystemProvider.readAttributes(path1, CloudStorageFileAttributes.class))
873+
.thenReturn(attributesGcs);
874+
875+
Map<String, Object> expectedBasic = new TreeMap<>();
876+
// BasicFileAttributes
877+
expectedBasic.put("creationTime", FileTime.fromMillis(1L));
878+
expectedBasic.put("lastModifiedTime", FileTime.fromMillis(2L));
879+
expectedBasic.put("lastAccessTime", FileTime.fromMillis(3L));
880+
expectedBasic.put("isRegularFile", true);
881+
expectedBasic.put("isDirectory", true);
882+
expectedBasic.put("isSymbolicLink", true);
883+
expectedBasic.put("isOther", true);
884+
expectedBasic.put("size", 42L);
885+
886+
assertEquals(expectedBasic, fileSystemProvider.readAttributes(path1, "basic:*"));
887+
888+
Map<String, Object> expectedGcs = new TreeMap<>(expectedBasic);
889+
// CloudStorageFileAttributes
890+
expectedGcs.put("etag", Optional.of("TheEtag"));
891+
expectedGcs.put("mimeType", Optional.of("TheMimeType"));
892+
expectedGcs.put("acl", Optional.of(acls));
893+
expectedGcs.put("cacheControl", Optional.of("TheCacheControl"));
894+
expectedGcs.put("contentEncoding", Optional.of("TheContentEncoding"));
895+
expectedGcs.put("contentDisposition", Optional.of("TheContentDisposition"));
896+
expectedGcs.put("userMetadata", new TreeMap<>());
897+
898+
assertEquals(expectedGcs, fileSystemProvider.readAttributes(path1, "gcs:*"));
899+
900+
Map<String, Object> expectedSpecific = new TreeMap<>();
901+
expectedSpecific.put("lastModifiedTime", FileTime.fromMillis(2L));
902+
expectedSpecific.put("isSymbolicLink", true);
903+
expectedSpecific.put("isOther", true);
904+
905+
// Asking for attributes that should NOT be known because we ask for basic view !
906+
assertEquals(
907+
expectedSpecific,
908+
fileSystemProvider.readAttributes(
909+
path1, "basic:lastModifiedTime,isSymbolicLink,isOther,etag,cacheControl"));
910+
911+
// Add the attributes that are only known in gcs view
912+
expectedSpecific.put("etag", Optional.of("TheEtag"));
913+
expectedSpecific.put("cacheControl", Optional.of("TheCacheControl"));
914+
915+
assertEquals(
916+
expectedSpecific,
917+
fileSystemProvider.readAttributes(
918+
path1, "gcs:lastModifiedTime,isSymbolicLink,isOther,etag,cacheControl"));
919+
}
920+
820921
private static CloudStorageConfiguration permitEmptyPathComponents(boolean value) {
821922
return CloudStorageConfiguration.builder().permitEmptyPathComponents(value).build();
822923
}

0 commit comments

Comments
 (0)