Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public abstract class CloudStorageConfiguration {
* <li>Performing I/O on paths with extra slashes, e.g. {@code a//b} will throw an error.
* <li>The prefix slash on absolute paths will be removed when converting to an object name.
* <li>Pseudo-directories are enabled, so any path with a trailing slash is a fake directory.
* <li>Channel re-opens are disabled.
* </ul>
*/
public static Builder builder() {
Expand Down Expand Up @@ -159,11 +160,27 @@ public CloudStorageConfiguration build() {
maxChannelReopens);
}

Builder(CloudStorageConfiguration toModify) {
workingDirectory = toModify.workingDirectory();
permitEmptyPathComponents = toModify.permitEmptyPathComponents();
stripPrefixSlash = toModify.stripPrefixSlash();
usePseudoDirectories = toModify.usePseudoDirectories();
blockSize = toModify.blockSize();
maxChannelReopens = toModify.maxChannelReopens();
}

Builder() {}
}

static CloudStorageConfiguration fromMap(Map<String, ?> env) {
Builder builder = builder();
return fromMap(builder(), env);
}

static CloudStorageConfiguration fromMap(CloudStorageConfiguration defaultValues, Map<String, ?> env) {
return fromMap(new Builder(defaultValues), env);
}

static private CloudStorageConfiguration fromMap(Builder builder, Map<String, ?> env) {
for (Map.Entry<String, ?> entry : env.entrySet()) {
switch (entry.getKey()) {
case "workingDirectory":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,27 @@ public final class CloudStorageFileSystem extends FileSystem {
private final String bucket;
private final CloudStorageConfiguration config;

// Users can change this: then this affects every filesystem object created
// later, including via SPI. This is meant to be done only once, at the beginning
// of some main program, in order to force all libraries to use some settings we like.
// Libraries should never call this. It'll cause surprise to the writers of the main
// program and they'll be unhappy. Instead, create your own filesystem object with the
// right configuration and pass it along.
private static CloudStorageConfiguration userSpecifiedDefault = CloudStorageConfiguration.DEFAULT;

// Don't call this one, call the one in CloudStorageFileSystemProvider.
static void setDefaultCloudStorageConfiguration(CloudStorageConfiguration config) {
if (null == config) {
userSpecifiedDefault = CloudStorageConfiguration.DEFAULT;
} else {
userSpecifiedDefault = config;
}
}

static CloudStorageConfiguration getDefaultCloudStorageConfiguration() {
return userSpecifiedDefault;
}

/**
* Returns Google Cloud Storage {@link FileSystem} object for {@code bucket}.
*
Expand All @@ -79,7 +100,7 @@ public final class CloudStorageFileSystem extends FileSystem {
*/
@CheckReturnValue
public static CloudStorageFileSystem forBucket(String bucket) {
return forBucket(bucket, CloudStorageConfiguration.DEFAULT);
return forBucket(bucket, userSpecifiedDefault);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,30 @@ public static void setStorageOptions(StorageOptions newStorageOptions) {
futureStorageOptions = newStorageOptions;
}

/**
* Changes the default configuration for every filesystem object created
* from here on, including via SPI. If null then future filesystem objects
* will have the factory default configuration.
*
* <p>If options are specified later then they override the defaults.
* Methods that take a whole CloudStorageConfiguration (eg.
* CloudStorageFileSystem.forBucket) will completely override the defaults.
* Methods that take individual options (eg.
* CloudStorageFileSystemProvier.newFileSystem) will override only these options;
* the rest will be taken from the defaults specified here.
*
* <p>This is meant to be done only once, at the beginning of some main program,
* in order to force all libraries to use some settings we like.
*
* <p>Libraries should never call this. If you're a library then, instead, create your own
* filesystem object with the right configuration and pass it along.
*
* @param newDefault new default CloudStorageConfiguration
*/
public static void setDefaultCloudStorageConfiguration(CloudStorageConfiguration newDefault) {

This comment was marked as spam.

This comment was marked as spam.

CloudStorageFileSystem.setDefaultCloudStorageConfiguration(newDefault);
}

/**
* Default constructor which should only be called by Java SPI.
*
Expand Down Expand Up @@ -200,7 +224,11 @@ && isNullOrEmpty(uri.getUserInfo()),
uri);
CloudStorageUtil.checkBucket(uri.getHost());
initStorage();
return new CloudStorageFileSystem(this, uri.getHost(), CloudStorageConfiguration.fromMap(env));
return new CloudStorageFileSystem(
this,
uri.getHost(),
CloudStorageConfiguration.fromMap(
CloudStorageFileSystem.getDefaultCloudStorageConfiguration(), env));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public static CloudStorageOption.OpenCopy withBlockSize(int size) {
}

/**
* Sets the max number of times that the channel can be reopen if reading
* Sets the max number of times that the channel can be reopened if reading
* fails because the channel unexpectedly closes.
*
* <p>The default is 0.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.google.cloud.storage.BlobInfo;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageException;
import com.google.common.annotations.VisibleForTesting;

import javax.annotation.CheckReturnValue;
import javax.annotation.concurrent.ThreadSafe;
Expand Down Expand Up @@ -49,7 +50,8 @@ final class CloudStorageReadChannel implements SeekableByteChannel {
private final Storage gcsStorage;
private final BlobId file;
// max # of times we may reopen the file
private final int maxChannelReopens;
@VisibleForTesting
final int maxChannelReopens;
// how many times we re-opened the file
private int reopens;
private ReadChannel channel;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

import java.io.IOException;
import java.net.URI;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
Expand Down Expand Up @@ -69,6 +70,57 @@ public void before() {
CloudStorageFileSystemProvider.setStorageOptions(LocalStorageHelper.getOptions());
}

@Test
public void checkDefaultOptions() throws IOException {

This comment was marked as spam.

This comment was marked as spam.

// 1. We get the normal default if we don't do anything special.
Path path = Paths.get(URI.create("gs://bucket/file"));
CloudStorageFileSystem gcs = (CloudStorageFileSystem)path.getFileSystem();
assertThat(gcs.config().maxChannelReopens()).isEqualTo(0);

// 2(a). Override the default, and see it reflected.
CloudStorageFileSystemProvider.setDefaultCloudStorageConfiguration(
CloudStorageConfiguration.builder()
.maxChannelReopens(123).build());
Path path2 = Paths.get(URI.create("gs://newbucket/file"));
CloudStorageFileSystem gcs2 = (CloudStorageFileSystem)path2.getFileSystem();
assertThat(gcs2.config().maxChannelReopens()).isEqualTo(123);

// 2(b) ...even reflected if we try to open a file.
try (FileSystem fs = CloudStorageFileSystem.forBucket("bucket")) {
CloudStorageFileSystem csfs = (CloudStorageFileSystem)fs;
assertThat(csfs.config().maxChannelReopens()).isEqualTo(123);
Files.write(fs.getPath("/angel"), ALONE.getBytes(UTF_8));
path2 = Paths.get(URI.create("gs://bucket/angel"));
try (SeekableByteChannel seekableByteChannel = Files.newByteChannel(path2)) {
CloudStorageReadChannel cloudChannel = (CloudStorageReadChannel) seekableByteChannel;
assertThat(cloudChannel.maxChannelReopens).isEqualTo(123);
}
}

// 4. Clean up.
CloudStorageFileSystemProvider.setDefaultCloudStorageConfiguration(null);
Path path3 = Paths.get(URI.create("gs://newbucket/file"));
CloudStorageFileSystem gcs3 = (CloudStorageFileSystem)path3.getFileSystem();
assertThat(gcs3.config().maxChannelReopens()).isEqualTo(0);
}

@Test
public void canOverrideDefaultOptions() throws IOException {
// Set a new default.
CloudStorageFileSystemProvider.setDefaultCloudStorageConfiguration(
CloudStorageConfiguration.builder()
.maxChannelReopens(123).build());

// This code wants its own value.
try (FileSystem fs = CloudStorageFileSystem.forBucket("bucket", CloudStorageConfiguration.builder().maxChannelReopens(7).build())) {
CloudStorageFileSystem csfs = (CloudStorageFileSystem)fs;
assertThat(csfs.config().maxChannelReopens()).isEqualTo(7);
}

// Clean up.
CloudStorageFileSystemProvider.setDefaultCloudStorageConfiguration(null);
}

@Test
public void testGetPath() throws IOException {
try (FileSystem fs = CloudStorageFileSystem.forBucket("bucket")) {
Expand Down