Skip to content

Commit 9b7d8f9

Browse files
committed
Make some final changes to the GCS NIO API
This change prepares GCS NIO to be merged into master, by making some last minute API changes. - Documentation has been added to many methods to clarify the preferred method for instantiating the library, which is the non-SPI version. - Unit tests have been updated to not rely on the SPI, because there's no way to guarantee clean isolation of SPI usage across tests. We'll be relying on integration testing to test the SPI interface. - The unit testing methodology has changed somewhat. FakeStorageRpc should be a private final field on the test class so, if desired, we'll be able to have the tests dip directly into fake memory. - IOException has been added back to the throws of file system close, in case we decide to implement the "close all owned channels" thing into that method in the future. - The getters on the configuration class have been made package-private, since there's no foreseeable reason they would be needed by the user. - Injectable constructors have been added for Dagger 2 users. In a future change, a README.md file will be added to replace the documentation in the package-info.java file.
1 parent e85256c commit 9b7d8f9

15 files changed

+931
-807
lines changed

gcloud-java-contrib/gcloud-java-nio/pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@
4949
<version>1.1</version>
5050
<scope>provided</scope> <!-- to leave out of the all-deps jar -->
5151
</dependency>
52+
<dependency>
53+
<groupId>com.google.auto.factory</groupId>
54+
<artifactId>auto-factory</artifactId>
55+
<version>1.0-beta3</version>
56+
<scope>provided</scope> <!-- to leave out of the all-deps jar -->
57+
</dependency>
5258
<dependency>
5359
<groupId>junit</groupId>
5460
<artifactId>junit</artifactId>

gcloud-java-contrib/gcloud-java-nio/src/main/java/com/google/gcloud/storage/contrib/nio/CloudStorageConfiguration.java

+24-36
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,26 @@
33
import static com.google.common.base.Preconditions.checkArgument;
44

55
import com.google.auto.value.AutoValue;
6+
import com.google.common.base.Strings;
67

78
import java.util.Map;
89

10+
import javax.annotation.Nullable;
11+
912
/**
1013
* Configuration for {@link CloudStorageFileSystem} instances.
1114
*/
1215
@AutoValue
1316
public abstract class CloudStorageConfiguration {
1417

15-
/**
16-
* Returns path of current working directory. This defaults to the root directory.
17-
*/
18-
public abstract String workingDirectory();
19-
20-
/**
21-
* Returns {@code true} if we <i>shouldn't</i> throw an exception when encountering object names
22-
* containing superfluous slashes, e.g. {@code a//b}.
23-
*/
24-
public abstract boolean permitEmptyPathComponents();
25-
26-
/**
27-
* Returns {@code true} if '/' prefix on absolute object names should be removed before I/O.
28-
*
29-
* <p>If you disable this feature, please take into consideration that all paths created from a
30-
* URI will have the leading slash.
31-
*/
32-
public abstract boolean stripPrefixSlash();
33-
34-
/**
35-
* Returns {@code true} if paths with a trailing slash should be treated as fake directories.
36-
*/
37-
public abstract boolean usePseudoDirectories();
18+
private static final CloudStorageConfiguration DEFAULT = builder().build();
3819

3920
/**
40-
* Returns block size (in bytes) used when talking to the GCS HTTP server.
21+
* Returns default GCS NIO configuration.
4122
*/
42-
public abstract int blockSize();
23+
public static CloudStorageConfiguration getDefault() {
24+
return DEFAULT;
25+
}
4326

4427
/**
4528
* Creates a new builder, initialized with the following settings:
@@ -54,22 +37,29 @@ public static Builder builder() {
5437
return new Builder();
5538
}
5639

40+
abstract String workingDirectory();
41+
abstract boolean permitEmptyPathComponents();
42+
abstract boolean stripPrefixSlash();
43+
abstract boolean usePseudoDirectories();
44+
abstract int blockSize();
45+
5746
/**
5847
* Builder for {@link CloudStorageConfiguration}.
5948
*/
6049
public static final class Builder {
6150

6251
private String workingDirectory = UnixPath.ROOT;
63-
private boolean permitEmptyPathComponents = false;
52+
private boolean permitEmptyPathComponents;
6453
private boolean stripPrefixSlash = true;
6554
private boolean usePseudoDirectories = true;
6655
private int blockSize = CloudStorageFileSystem.BLOCK_SIZE_DEFAULT;
6756

6857
/**
69-
* Changes current working directory for new filesystem. This cannot be changed once it's
70-
* been set. You'll need to create another {@link CloudStorageFileSystem} object.
58+
* Changes current working directory for new filesystem. This defaults to the root directory.
59+
* The working directory cannot be changed once it's been set. You'll need to create another
60+
* {@link CloudStorageFileSystem} object.
7161
*
72-
* @throws IllegalArgumentException if {@code path} is not absolute.
62+
* @throws IllegalArgumentException if {@code path} is not absolute
7363
*/
7464
public Builder workingDirectory(String path) {
7565
checkArgument(UnixPath.getPath(false, path).isAbsolute(), "not absolute: %s", path);
@@ -79,7 +69,7 @@ public Builder workingDirectory(String path) {
7969

8070
/**
8171
* Configures whether or not we should throw an exception when encountering object names
82-
* containing superfluous slashes, e.g. {@code a//b}
72+
* containing superfluous slashes, e.g. {@code a//b}.
8373
*/
8474
public Builder permitEmptyPathComponents(boolean value) {
8575
permitEmptyPathComponents = value;
@@ -130,15 +120,13 @@ public CloudStorageConfiguration build() {
130120
Builder() {}
131121
}
132122

133-
static final CloudStorageConfiguration DEFAULT = builder().build();
134-
135-
static CloudStorageConfiguration fromMap(Map<String, ?> env) {
123+
static CloudStorageConfiguration fromMap(@Nullable String workingDirectory, Map<String, ?> env) {
136124
Builder builder = builder();
125+
if (!Strings.isNullOrEmpty(workingDirectory)) {
126+
builder.workingDirectory(workingDirectory);
127+
}
137128
for (Map.Entry<String, ?> entry : env.entrySet()) {
138129
switch (entry.getKey()) {
139-
case "workingDirectory":
140-
builder.workingDirectory((String) entry.getValue());
141-
break;
142130
case "permitEmptyPathComponents":
143131
builder.permitEmptyPathComponents((Boolean) entry.getValue());
144132
break;

gcloud-java-contrib/gcloud-java-nio/src/main/java/com/google/gcloud/storage/contrib/nio/CloudStorageFileSystem.java

+53-24
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
import static com.google.common.base.Preconditions.checkArgument;
44
import static com.google.common.base.Preconditions.checkNotNull;
55

6+
import com.google.auto.factory.AutoFactory;
7+
import com.google.auto.factory.Provided;
8+
import com.google.common.base.Predicates;
69
import com.google.common.collect.ImmutableSet;
10+
import com.google.common.collect.Iterables;
711

812
import java.io.IOException;
913
import java.net.URI;
@@ -15,10 +19,12 @@
1519
import java.nio.file.WatchService;
1620
import java.nio.file.attribute.FileTime;
1721
import java.nio.file.attribute.UserPrincipalLookupService;
22+
import java.nio.file.spi.FileSystemProvider;
1823
import java.util.Objects;
1924
import java.util.Set;
2025

21-
import javax.annotation.concurrent.Immutable;
26+
import javax.annotation.CheckReturnValue;
27+
import javax.annotation.concurrent.ThreadSafe;
2228

2329
/**
2430
* Google Cloud Storage {@link FileSystem} implementation.
@@ -28,38 +34,48 @@
2834
* @see <a href="https://developers.google.com/storage/docs/bucketnaming">
2935
* Bucket and Object Naming Guidelines</a>
3036
*/
31-
@Immutable
37+
@ThreadSafe
3238
public final class CloudStorageFileSystem extends FileSystem {
3339

3440
/**
35-
* Returns Google Cloud Storage {@link FileSystem} object for {@code bucket}.
36-
*
37-
* <p><b>NOTE:</b> You may prefer to use Java's standard API instead:<pre> {@code
38-
*
39-
* FileSystem fs = FileSystems.getFileSystem(URI.create("gs://bucket"));}</pre>
40-
*
41-
* <p>However some systems and build environments might be flaky when it comes to Java SPI. This
42-
* is because services are generally runtime dependencies and depend on a META-INF file being
43-
* present in your jar (generated by Google Auto at compile-time). In such cases, this method
44-
* provides a simpler alternative.
45-
*
46-
* @see #forBucket(String, CloudStorageConfiguration)
47-
* @see java.nio.file.FileSystems#getFileSystem(java.net.URI)
41+
* Invokes {@link #forBucket(String, CloudStorageConfiguration)} with
42+
* {@link CloudStorageConfiguration#getDefault()}.
4843
*/
44+
@CheckReturnValue
4945
public static CloudStorageFileSystem forBucket(String bucket) {
50-
return forBucket(bucket, CloudStorageConfiguration.DEFAULT);
46+
return forBucket(bucket, CloudStorageConfiguration.getDefault());
5147
}
5248

5349
/**
54-
* Creates new file system instance for {@code bucket}, with customizable settings.
50+
* Returns Google Cloud Storage {@link FileSystem} object for {@code bucket}.
51+
*
52+
* <p>GCS file system objects are basically free. You can create as many as you want, even if you
53+
* have multiple instances for the same bucket. There's no actual system resources associated
54+
* with this object. Therefore calling {@link #close()} on the returned value is optional.
5555
*
56-
* @see #forBucket(String)
56+
* <p><b>Note:</b> It is also possible to instantiate this class via Java's Service Provider
57+
* Interface (SPI), e.g. {@code FileSystems.getFileSystem(URI.create("gs://bucket"))}. We
58+
* discourage you from using the SPI if possible, for the reasons documented in
59+
* {@link CloudStorageFileSystemProvider#newFileSystem(URI, java.util.Map)}
60+
*
61+
* @see #forBucket(String, CloudStorageConfiguration)
62+
* @see java.nio.file.FileSystems#getFileSystem(java.net.URI)
5763
*/
64+
@CheckReturnValue
5865
public static CloudStorageFileSystem forBucket(String bucket, CloudStorageConfiguration config) {
66+
checkNotNull(config);
5967
checkArgument(
6068
!bucket.startsWith(URI_SCHEME + ":"), "Bucket name must not have schema: %s", bucket);
6169
return new CloudStorageFileSystem(
62-
new CloudStorageFileSystemProvider(), bucket, checkNotNull(config));
70+
// XXX: This is a kludge to get the provider instance from the SPI. This is necessary since
71+
// the behavior of NIO changes quite a bit if the provider instances aren't the same.
72+
(CloudStorageFileSystemProvider)
73+
Iterables.getOnlyElement(
74+
Iterables.filter(
75+
FileSystemProvider.installedProviders(),
76+
Predicates.instanceOf(CloudStorageFileSystemProvider.class))),
77+
config,
78+
bucket);
6379
}
6480

6581
public static final String URI_SCHEME = "gs";
@@ -69,12 +85,15 @@ public static CloudStorageFileSystem forBucket(String bucket, CloudStorageConfig
6985
public static final FileTime FILE_TIME_UNKNOWN = FileTime.fromMillis(0);
7086
public static final ImmutableSet<String> SUPPORTED_VIEWS = ImmutableSet.of(BASIC_VIEW, GCS_VIEW);
7187

72-
private final CloudStorageFileSystemProvider provider;
7388
private final String bucket;
89+
private final CloudStorageFileSystemProvider provider;
7490
private final CloudStorageConfiguration config;
7591

92+
@AutoFactory
7693
CloudStorageFileSystem(
77-
CloudStorageFileSystemProvider provider, String bucket, CloudStorageConfiguration config) {
94+
@Provided CloudStorageFileSystemProvider provider,
95+
@Provided CloudStorageConfiguration config,
96+
String bucket) {
7897
checkArgument(!bucket.isEmpty(), "bucket");
7998
this.provider = provider;
8099
this.bucket = bucket;
@@ -113,13 +132,20 @@ public CloudStoragePath getPath(String first, String... more) {
113132
}
114133

115134
/**
116-
* Does nothing.
135+
* Does nothing currently. This method <i>might</i> be updated in the future to close all channels
136+
* associated with this file system object. However it's unlikely that even then, calling this
137+
* method will become mandatory.
117138
*/
118139
@Override
119-
public void close() {}
140+
public void close() throws IOException {
141+
// TODO(jean-philippe-martin,jart): Synchronously close all active channels associated with this
142+
// FileSystem instance on close, per NIO documentation. But we
143+
// probably shouldn't bother unless a legitimate reason can be
144+
// found to implement this behavior.
145+
}
120146

121147
/**
122-
* Returns {@code true}.
148+
* Returns {@code true}, even if you previously called the {@link #close()} method.
123149
*/
124150
@Override
125151
public boolean isOpen() {
@@ -147,6 +173,9 @@ public Iterable<Path> getRootDirectories() {
147173
return ImmutableSet.<Path>of(CloudStoragePath.getPath(this, UnixPath.ROOT));
148174
}
149175

176+
/**
177+
* Returns nothing because GCS doesn't have disk partitions of limited size, or anything similar.
178+
*/
150179
@Override
151180
public Iterable<FileStore> getFileStores() {
152181
return ImmutableSet.of();

0 commit comments

Comments
 (0)