+ */
+ public Builder setCustomTime(Long customTime) {
+ throw new UnsupportedOperationException(
+ "Override setCustomTime with your own implementation,"
+ + " or use com.google.cloud.storage.Blob.");
+ }
+
/**
* Sets the CRC32C checksum of blob's data as described in RFC 4960, Appendix B; from hex
@@ -325,6 +346,7 @@ static final class BuilderImpl extends Builder {
private String selfLink;
private String md5;
private String crc32c;
+ private Long customTime;
private String mediaLink;
private Map metadata;
private Long metageneration;
@@ -360,6 +382,7 @@ static final class BuilderImpl extends Builder {
selfLink = blobInfo.selfLink;
md5 = blobInfo.md5;
crc32c = blobInfo.crc32c;
+ customTime = blobInfo.customTime;
mediaLink = blobInfo.mediaLink;
metadata = blobInfo.metadata;
metageneration = blobInfo.metageneration;
@@ -488,6 +511,12 @@ public Builder setCrc32c(String crc32c) {
return this;
}
+ @Override
+ public Builder setCustomTime(Long customTime) {
+ this.customTime = customTime;
+ return this;
+ }
+
@Override
public Builder setCrc32cFromHexString(String crc32cHexString) {
if (crc32cHexString == null) {
@@ -619,6 +648,7 @@ public BlobInfo build() {
selfLink = builder.selfLink;
md5 = builder.md5;
crc32c = builder.crc32c;
+ customTime = builder.customTime;
mediaLink = builder.mediaLink;
metadata = builder.metadata;
metageneration = builder.metageneration;
@@ -857,6 +887,11 @@ public Long getCreateTime() {
return createTime;
}
+ /** Returns the custom time specified by the user for an object. */
+ public Long getCustomTime() {
+ return customTime;
+ }
+
/**
* Returns {@code true} if the current blob represents a directory. This can only happen if the
* blob is returned by {@link Storage#list(String, Storage.BlobListOption...)} when the {@link
@@ -1002,6 +1037,9 @@ public ObjectAccessControl apply(Acl acl) {
if (createTime != null) {
storageObject.setTimeCreated(new DateTime(createTime));
}
+ if (customTime != null) {
+ storageObject.setCustomTime(new DateTime(customTime));
+ }
if (size != null) {
storageObject.setSize(BigInteger.valueOf(size));
}
@@ -1125,6 +1163,9 @@ static BlobInfo fromPb(StorageObject storageObject) {
if (storageObject.getTimeCreated() != null) {
builder.setCreateTime(storageObject.getTimeCreated().getValue());
}
+ if (storageObject.getCustomTime() != null) {
+ builder.setCustomTime(storageObject.getCustomTime().getValue());
+ }
if (storageObject.getSize() != null) {
builder.setSize(storageObject.getSize().longValue());
}
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java
index c64a3dc5e..3714e6e94 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java
@@ -601,6 +601,12 @@ Builder setCreateTime(Long createTime) {
return this;
}
+ @Override
+ Builder setUpdateTime(Long updateTime) {
+ infoBuilder.setUpdateTime(updateTime);
+ return this;
+ }
+
@Override
Builder setMetageneration(Long metageneration) {
infoBuilder.setMetageneration(metageneration);
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
index 9a716d702..602be08ac 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java
@@ -84,6 +84,7 @@ public com.google.api.services.storage.model.Bucket apply(BucketInfo bucketInfo)
private final List lifecycleRules;
private final String etag;
private final Long createTime;
+ private final Long updateTime;
private final Long metageneration;
private final List cors;
private final List acl;
@@ -350,7 +351,11 @@ public LifecycleRule(LifecycleAction action, LifecycleCondition condition) {
&& condition.getAge() == null
&& condition.getCreatedBefore() == null
&& condition.getMatchesStorageClass() == null
- && condition.getNumberOfNewerVersions() == null) {
+ && condition.getNumberOfNewerVersions() == null
+ && condition.getDaysSinceNoncurrentTime() == null
+ && condition.getNoncurrentTimeBefore() == null
+ && condition.getCustomTimeBefore() == null
+ && condition.getDaysSinceCustomTime() == null) {
throw new IllegalArgumentException(
"You must specify at least one condition to use object lifecycle "
+ "management. Please see https://cloud.google.com/storage/docs/lifecycle for details.");
@@ -418,7 +423,18 @@ Rule toPb() {
? null
: transform(
lifecycleCondition.getMatchesStorageClass(),
- Functions.toStringFunction()));
+ Functions.toStringFunction()))
+ .setDaysSinceNoncurrentTime(lifecycleCondition.getDaysSinceNoncurrentTime())
+ .setNoncurrentTimeBefore(
+ lifecycleCondition.getNoncurrentTimeBefore() == null
+ ? null
+ : new DateTime(
+ true, lifecycleCondition.getNoncurrentTimeBefore().getValue(), 0))
+ .setCustomTimeBefore(
+ lifecycleCondition.getCustomTimeBefore() == null
+ ? null
+ : new DateTime(true, lifecycleCondition.getCustomTimeBefore().getValue(), 0))
+ .setDaysSinceCustomTime(lifecycleCondition.getDaysSinceCustomTime());
rule.setCondition(condition);
@@ -461,7 +477,11 @@ static LifecycleRule fromPb(Rule rule) {
public StorageClass apply(String storageClass) {
return StorageClass.valueOf(storageClass);
}
- }));
+ }))
+ .setDaysSinceNoncurrentTime(condition.getDaysSinceNoncurrentTime())
+ .setNoncurrentTimeBefore(condition.getNoncurrentTimeBefore())
+ .setCustomTimeBefore(condition.getCustomTimeBefore())
+ .setDaysSinceCustomTime(condition.getDaysSinceCustomTime());
return new LifecycleRule(lifecycleAction, conditionBuilder.build());
}
@@ -479,6 +499,10 @@ public static class LifecycleCondition implements Serializable {
private final Integer numberOfNewerVersions;
private final Boolean isLive;
private final List matchesStorageClass;
+ private final Integer daysSinceNoncurrentTime;
+ private final DateTime noncurrentTimeBefore;
+ private final DateTime customTimeBefore;
+ private final Integer daysSinceCustomTime;
private LifecycleCondition(Builder builder) {
this.age = builder.age;
@@ -486,6 +510,10 @@ private LifecycleCondition(Builder builder) {
this.numberOfNewerVersions = builder.numberOfNewerVersions;
this.isLive = builder.isLive;
this.matchesStorageClass = builder.matchesStorageClass;
+ this.daysSinceNoncurrentTime = builder.daysSinceNoncurrentTime;
+ this.noncurrentTimeBefore = builder.noncurrentTimeBefore;
+ this.customTimeBefore = builder.customTimeBefore;
+ this.daysSinceCustomTime = builder.daysSinceCustomTime;
}
public Builder toBuilder() {
@@ -494,7 +522,11 @@ public Builder toBuilder() {
.setCreatedBefore(this.createdBefore)
.setNumberOfNewerVersions(this.numberOfNewerVersions)
.setIsLive(this.isLive)
- .setMatchesStorageClass(this.matchesStorageClass);
+ .setMatchesStorageClass(this.matchesStorageClass)
+ .setDaysSinceNoncurrentTime(this.daysSinceNoncurrentTime)
+ .setNoncurrentTimeBefore(this.noncurrentTimeBefore)
+ .setCustomTimeBefore(this.customTimeBefore)
+ .setDaysSinceCustomTime(this.daysSinceCustomTime);
}
public static Builder newBuilder() {
@@ -509,6 +541,10 @@ public String toString() {
.add("numberofNewerVersions", numberOfNewerVersions)
.add("isLive", isLive)
.add("matchesStorageClass", matchesStorageClass)
+ .add("daysSinceNoncurrentTime", daysSinceNoncurrentTime)
+ .add("noncurrentTimeBefore", noncurrentTimeBefore)
+ .add("customTimeBefore", customTimeBefore)
+ .add("daysSinceCustomTime", daysSinceCustomTime)
.toString();
}
@@ -532,6 +568,28 @@ public List getMatchesStorageClass() {
return matchesStorageClass;
}
+ /** Returns the number of days elapsed since the noncurrent timestamp of an object. */
+ public Integer getDaysSinceNoncurrentTime() {
+ return daysSinceNoncurrentTime;
+ }
+
+ /**
+ * Returns the date in RFC 3339 format with only the date part (for instance, "2013-01-15").
+ */
+ public DateTime getNoncurrentTimeBefore() {
+ return noncurrentTimeBefore;
+ }
+
+ /* Returns the date in RFC 3339 format with only the date part (for instance, "2013-01-15").*/
+ public DateTime getCustomTimeBefore() {
+ return customTimeBefore;
+ }
+
+ /** Returns the number of days elapsed since the user-specified timestamp set on an object. */
+ public Integer getDaysSinceCustomTime() {
+ return daysSinceCustomTime;
+ }
+
/** Builder for {@code LifecycleCondition}. */
public static class Builder {
private Integer age;
@@ -539,6 +597,10 @@ public static class Builder {
private Integer numberOfNewerVersions;
private Boolean isLive;
private List matchesStorageClass;
+ private Integer daysSinceNoncurrentTime;
+ private DateTime noncurrentTimeBefore;
+ private DateTime customTimeBefore;
+ private Integer daysSinceCustomTime;
private Builder() {}
@@ -593,6 +655,50 @@ public Builder setMatchesStorageClass(List matchesStorageClass) {
return this;
}
+ /**
+ * Sets the number of days elapsed since the noncurrent timestamp of an object. The
+ * condition is satisfied if the days elapsed is at least this number. This condition is
+ * relevant only for versioned objects. The value of the field must be a nonnegative
+ * integer. If it's zero, the object version will become eligible for Lifecycle action as
+ * soon as it becomes noncurrent.
+ */
+ public Builder setDaysSinceNoncurrentTime(Integer daysSinceNoncurrentTime) {
+ this.daysSinceNoncurrentTime = daysSinceNoncurrentTime;
+ return this;
+ }
+
+ /**
+ * Sets the date in RFC 3339 format with only the date part (for instance, "2013-01-15").
+ * Note that only date part will be considered, if the time is specified it will be
+ * truncated. This condition is satisfied when the noncurrent time on an object is before
+ * this date. This condition is relevant only for versioned objects.
+ */
+ public Builder setNoncurrentTimeBefore(DateTime noncurrentTimeBefore) {
+ this.noncurrentTimeBefore = noncurrentTimeBefore;
+ return this;
+ }
+
+ /**
+ * Sets the date in RFC 3339 format with only the date part (for instance, "2013-01-15").
+ * Note that only date part will be considered, if the time is specified it will be
+ * truncated. This condition is satisfied when the custom time on an object is before this
+ * date in UTC.
+ */
+ public Builder setCustomTimeBefore(DateTime customTimeBefore) {
+ this.customTimeBefore = customTimeBefore;
+ return this;
+ }
+
+ /**
+ * Sets the number of days elapsed since the user-specified timestamp set on an object. The
+ * condition is satisfied if the days elapsed is at least this number. If no custom
+ * timestamp is specified on an object, the condition does not apply.
+ */
+ public Builder setDaysSinceCustomTime(Integer daysSinceCustomTime) {
+ this.daysSinceCustomTime = daysSinceCustomTime;
+ return this;
+ }
+
/** Builds a {@code LifecycleCondition} object. * */
public LifecycleCondition build() {
return new LifecycleCondition(this);
@@ -1005,6 +1111,8 @@ public abstract static class Builder {
abstract Builder setCreateTime(Long createTime);
+ abstract Builder setUpdateTime(Long updateTime);
+
abstract Builder setMetageneration(Long metageneration);
abstract Builder setLocationType(String locationType);
@@ -1090,6 +1198,7 @@ static final class BuilderImpl extends Builder {
private String location;
private String etag;
private Long createTime;
+ private Long updateTime;
private Long metageneration;
private List cors;
private List acl;
@@ -1113,6 +1222,7 @@ static final class BuilderImpl extends Builder {
name = bucketInfo.name;
etag = bucketInfo.etag;
createTime = bucketInfo.createTime;
+ updateTime = bucketInfo.updateTime;
metageneration = bucketInfo.metageneration;
location = bucketInfo.location;
storageClass = bucketInfo.storageClass;
@@ -1232,6 +1342,12 @@ Builder setCreateTime(Long createTime) {
return this;
}
+ @Override
+ Builder setUpdateTime(Long updateTime) {
+ this.updateTime = updateTime;
+ return this;
+ }
+
@Override
Builder setMetageneration(Long metageneration) {
this.metageneration = metageneration;
@@ -1240,7 +1356,7 @@ Builder setMetageneration(Long metageneration) {
@Override
public Builder setCors(Iterable cors) {
- this.cors = cors != null ? ImmutableList.copyOf(cors) : null;
+ this.cors = cors != null ? ImmutableList.copyOf(cors) : ImmutableList.of();
return this;
}
@@ -1337,6 +1453,7 @@ public BucketInfo build() {
name = builder.name;
etag = builder.etag;
createTime = builder.createTime;
+ updateTime = builder.updateTime;
metageneration = builder.metageneration;
location = builder.location;
storageClass = builder.storageClass;
@@ -1468,6 +1585,14 @@ public Long getCreateTime() {
return createTime;
}
+ /**
+ * Returns the last modification time of the bucket's metadata expressed as the number of
+ * milliseconds since the Unix epoch.
+ */
+ public Long getUpdateTime() {
+ return updateTime;
+ }
+
/** Returns the metadata generation of this bucket. */
public Long getMetageneration() {
return metageneration;
@@ -1650,6 +1775,9 @@ com.google.api.services.storage.model.Bucket toPb() {
if (createTime != null) {
bucketPb.setTimeCreated(new DateTime(createTime));
}
+ if (updateTime != null) {
+ bucketPb.setUpdated(new DateTime(updateTime));
+ }
if (metageneration != null) {
bucketPb.setMetageneration(metageneration);
}
@@ -1797,6 +1925,9 @@ static BucketInfo fromPb(com.google.api.services.storage.model.Bucket bucketPb)
if (bucketPb.getTimeCreated() != null) {
builder.setCreateTime(bucketPb.getTimeCreated().getValue());
}
+ if (bucketPb.getUpdated() != null) {
+ builder.setUpdateTime(bucketPb.getUpdated().getValue());
+ }
if (bucketPb.getLocation() != null) {
builder.setLocation(bucketPb.getLocation());
}
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/PostPolicyV4.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/PostPolicyV4.java
index df2936dc9..96afca06e 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/PostPolicyV4.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/PostPolicyV4.java
@@ -18,56 +18,125 @@
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
/**
- * Presigned V4 post policy.
+ * Presigned V4 post policy. Instances of {@code PostPolicyV4} include a URL and a map of fields
+ * that can be specified in an HTML form to submit a POST request to upload an object.
*
- * @see POST Object
+ *
See POST Object for
+ * details of upload by using HTML forms.
+ *
+ *
See {@link Storage#generateSignedPostPolicyV4(BlobInfo, long, TimeUnit,
+ * PostPolicyV4.PostFieldsV4, PostPolicyV4.PostConditionsV4, Storage.PostPolicyV4Option...)} for
+ * example of usage.
*/
public final class PostPolicyV4 {
- private String url;
- private Map fields;
+ private final String url;
+ private final Map fields;
private PostPolicyV4(String url, Map fields) {
+ try {
+ if (!new URI(url).isAbsolute()) {
+ throw new IllegalArgumentException(url + " is not an absolute URL");
+ }
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(e);
+ }
+ PostFieldsV4.validateFields(fields);
+
this.url = url;
- this.fields = fields;
+ this.fields = Collections.unmodifiableMap(fields);
}
+ /**
+ * Constructs {@code PostPolicyV4} instance of the given URL and fields map.
+ *
+ * @param url URL for the HTTP POST request
+ * @param fields HTML form fields
+ * @return constructed object
+ * @throws IllegalArgumentException if URL is malformed or fields are not valid
+ */
public static PostPolicyV4 of(String url, Map fields) {
return new PostPolicyV4(url, fields);
}
+ /** Returns the URL for the HTTP POST request */
public String getUrl() {
return url;
}
+ /** Returns the HTML form fields */
public Map getFields() {
return fields;
}
/**
- * Class representing which fields to specify in a V4 POST request.
+ * A helper class to define fields to be specified in a V4 POST request. Instance of this class
+ * helps to construct {@code PostPolicyV4} objects. Used in: {@link
+ * Storage#generateSignedPostPolicyV4(BlobInfo, long, TimeUnit, PostPolicyV4.PostFieldsV4,
+ * PostPolicyV4.PostConditionsV4, Storage.PostPolicyV4Option...)}.
*
* @see POST
* Object Form fields
*/
public static final class PostFieldsV4 {
- private Map fieldsMap;
+ private final Map fieldsMap;
+ private static final List VALID_FIELDS =
+ Arrays.asList(
+ "acl",
+ "bucket",
+ "cache-control",
+ "content-disposition",
+ "content-encoding",
+ "content-type",
+ "expires",
+ "file",
+ "key",
+ "policy",
+ "success_action_redirect",
+ "success_action_status",
+ "x-goog-algorithm",
+ "x-goog-credential",
+ "x-goog-date",
+ "x-goog-signature");
+
+ private static void validateFields(Map fields) {
+ for (String key : fields.keySet()) {
+ if (!VALID_FIELDS.contains(key.toLowerCase())
+ && !key.startsWith(Builder.CUSTOM_FIELD_PREFIX)) {
+ throw new IllegalArgumentException("Invalid key: " + key);
+ }
+ }
+ }
private PostFieldsV4(Builder builder) {
- this.fieldsMap = builder.fieldsMap;
+ this(builder.fieldsMap);
}
private PostFieldsV4(Map fields) {
- this.fieldsMap = fields;
+ validateFields(fields);
+ this.fieldsMap = Collections.unmodifiableMap(fields);
}
+ /**
+ * Constructs {@code PostPolicyV4.PostFieldsV4} object of the given field map.
+ *
+ * @param fields a map of the HTML form fields
+ * @return constructed object
+ * @throws IllegalArgumentException if an unsupported field is specified
+ */
public static PostFieldsV4 of(Map fields) {
return new PostFieldsV4(fields);
}
@@ -81,10 +150,11 @@ public Map getFieldsMap() {
}
public static class Builder {
- private Map fieldsMap;
+ private static final String CUSTOM_FIELD_PREFIX = "x-goog-meta-";
+ private final Map fieldsMap;
private Builder() {
- fieldsMap = new HashMap<>();
+ this.fieldsMap = new HashMap<>();
}
public PostFieldsV4 build() {
@@ -111,8 +181,14 @@ public Builder setContentEncoding(String contentEncoding) {
return this;
}
+ /**
+ * @deprecated Invocation of this method has no effect, because all valid HTML form fields
+ * except Content-Length can use exact matching. Use {@link
+ * PostPolicyV4.PostConditionsV4.Builder#addContentLengthRangeCondition(int, int)} to
+ * specify a range for the content-length.
+ */
+ @Deprecated
public Builder setContentLength(int contentLength) {
- fieldsMap.put("content-length", "" + contentLength);
return this;
}
@@ -121,7 +197,13 @@ public Builder setContentType(String contentType) {
return this;
}
+ /** @deprecated Use {@link #setExpires(String)}. */
+ @Deprecated
public Builder Expires(String expires) {
+ return setExpires(expires);
+ }
+
+ public Builder setExpires(String expires) {
fieldsMap.put("expires", expires);
return this;
}
@@ -136,15 +218,26 @@ public Builder setSuccessActionStatus(int successActionStatus) {
return this;
}
+ /** @deprecated Use {@link #setCustomMetadataField(String, String)}. */
+ @Deprecated
public Builder AddCustomMetadataField(String field, String value) {
- fieldsMap.put("x-goog-meta-" + field, value);
+ return setCustomMetadataField(field, value);
+ }
+
+ public Builder setCustomMetadataField(String field, String value) {
+ if (!field.startsWith(CUSTOM_FIELD_PREFIX)) {
+ field = CUSTOM_FIELD_PREFIX + field;
+ }
+ fieldsMap.put(field, value);
return this;
}
}
}
/**
- * Class for specifying conditions in a V4 POST Policy document.
+ * A helper class for specifying conditions in a V4 POST Policy document. Used in: {@link
+ * Storage#generateSignedPostPolicyV4(BlobInfo, long, TimeUnit, PostPolicyV4.PostFieldsV4,
+ * PostPolicyV4.PostConditionsV4, Storage.PostPolicyV4Option...)}.
*
* @see
* Policy document
@@ -167,14 +260,14 @@ public static Builder newBuilder() {
}
public Set getConditions() {
- return conditions;
+ return Collections.unmodifiableSet(conditions);
}
public static class Builder {
- Set conditions;
+ private final Set conditions;
private Builder() {
- this.conditions = new LinkedHashSet<>();
+ this(new LinkedHashSet());
}
private Builder(Set conditions) {
@@ -190,64 +283,93 @@ public PostConditionsV4 build() {
}
public Builder addAclCondition(ConditionV4Type type, String acl) {
+ checkType(type, "acl");
conditions.add(new ConditionV4(type, "acl", acl));
return this;
}
public Builder addBucketCondition(ConditionV4Type type, String bucket) {
+ checkType(type, "bucket");
conditions.add(new ConditionV4(type, "bucket", bucket));
return this;
}
public Builder addCacheControlCondition(ConditionV4Type type, String cacheControl) {
+ checkType(type, "cache-control");
conditions.add(new ConditionV4(type, "cache-control", cacheControl));
return this;
}
public Builder addContentDispositionCondition(
ConditionV4Type type, String contentDisposition) {
+ checkType(type, "content-disposition");
conditions.add(new ConditionV4(type, "content-disposition", contentDisposition));
return this;
}
public Builder addContentEncodingCondition(ConditionV4Type type, String contentEncoding) {
+ checkType(type, "content-encoding");
conditions.add(new ConditionV4(type, "content-encoding", contentEncoding));
return this;
}
+ /**
+ * @deprecated Invocation of this method has no effect. Use {@link
+ * #addContentLengthRangeCondition(int, int)} to specify a range for the content-length.
+ */
public Builder addContentLengthCondition(ConditionV4Type type, int contentLength) {
- conditions.add(new ConditionV4(type, "content-length", "" + contentLength));
return this;
}
public Builder addContentTypeCondition(ConditionV4Type type, String contentType) {
+ checkType(type, "content-type");
conditions.add(new ConditionV4(type, "content-type", contentType));
return this;
}
+ /** @deprecated Use {@link #addExpiresCondition(long)} */
+ @Deprecated
public Builder addExpiresCondition(ConditionV4Type type, long expires) {
- conditions.add(new ConditionV4(type, "expires", dateFormat.format(expires)));
- return this;
+ return addExpiresCondition(expires);
}
+ /** @deprecated Use {@link #addExpiresCondition(String)} */
+ @Deprecated
public Builder addExpiresCondition(ConditionV4Type type, String expires) {
- conditions.add(new ConditionV4(type, "expires", expires));
+ return addExpiresCondition(expires);
+ }
+
+ public Builder addExpiresCondition(long expires) {
+ return addExpiresCondition(dateFormat.format(expires));
+ }
+
+ public Builder addExpiresCondition(String expires) {
+ conditions.add(new ConditionV4(ConditionV4Type.MATCHES, "expires", expires));
return this;
}
public Builder addKeyCondition(ConditionV4Type type, String key) {
+ checkType(type, "key");
conditions.add(new ConditionV4(type, "key", key));
return this;
}
public Builder addSuccessActionRedirectUrlCondition(
ConditionV4Type type, String successActionRedirectUrl) {
+ checkType(type, "success_action_redirect");
conditions.add(new ConditionV4(type, "success_action_redirect", successActionRedirectUrl));
return this;
}
+ /** @deprecated Use {@link #addSuccessActionStatusCondition(int)} */
+ @Deprecated
public Builder addSuccessActionStatusCondition(ConditionV4Type type, int status) {
- conditions.add(new ConditionV4(type, "success_action_status", "" + status));
+ return addSuccessActionStatusCondition(status);
+ }
+
+ public Builder addSuccessActionStatusCondition(int status) {
+ conditions.add(
+ new ConditionV4(ConditionV4Type.MATCHES, "success_action_status", "" + status));
return this;
}
@@ -260,18 +382,24 @@ Builder addCustomCondition(ConditionV4Type type, String field, String value) {
conditions.add(new ConditionV4(type, field, value));
return this;
}
+
+ private void checkType(ConditionV4Type type, String field) {
+ if (type != ConditionV4Type.MATCHES && type != ConditionV4Type.STARTS_WITH) {
+ throw new IllegalArgumentException("Field " + field + " can't use " + type);
+ }
+ }
}
}
/**
- * Class for a V4 POST Policy document.
+ * Class for a V4 POST Policy document. Used by Storage to construct {@code PostPolicyV4} objects.
*
* @see
* Policy document
*/
public static final class PostPolicyV4Document {
- private String expiration;
- private PostConditionsV4 conditions;
+ private final String expiration;
+ private final PostConditionsV4 conditions;
private PostPolicyV4Document(String expiration, PostConditionsV4 conditions) {
this.expiration = expiration;
@@ -351,9 +479,20 @@ public String toJson() {
}
public enum ConditionV4Type {
- MATCHES,
- STARTS_WITH,
- CONTENT_LENGTH_RANGE
+ MATCHES("eq"),
+ STARTS_WITH("starts-with"),
+ CONTENT_LENGTH_RANGE("content-length-range");
+
+ private final String name;
+
+ ConditionV4Type(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
}
/**
@@ -362,12 +501,12 @@ public enum ConditionV4Type {
* @see
* Policy document
*/
- static final class ConditionV4 {
- ConditionV4Type type;
- String operand1;
- String operand2;
+ public static final class ConditionV4 {
+ public final ConditionV4Type type;
+ public final String operand1;
+ public final String operand2;
- private ConditionV4(ConditionV4Type type, String operand1, String operand2) {
+ ConditionV4(ConditionV4Type type, String operand1, String operand2) {
this.type = type;
this.operand1 = operand1;
this.operand2 = operand2;
@@ -385,5 +524,18 @@ public boolean equals(Object other) {
public int hashCode() {
return Objects.hash(type, operand1, operand2);
}
+
+ /**
+ * Examples of returned strings: {@code ["eq", "$key", "test-object"]}, {@code ["starts-with",
+ * "$acl", "public"]}, {@code ["content-length-range", 246, 266]}.
+ */
+ @Override
+ public String toString() {
+ String body =
+ type == ConditionV4Type.CONTENT_LENGTH_RANGE
+ ? operand1 + ", " + operand2
+ : "\"$" + operand1 + "\", \"" + operand2 + "\"";
+ return "[\"" + type + "\", " + body + "]";
+ }
}
}
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
index eb15dd37a..6b8fc4990 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
@@ -521,6 +521,15 @@ public static BlobTargetOption disableGzipContent() {
return new BlobTargetOption(StorageRpc.Option.IF_DISABLE_GZIP_CONTENT, true);
}
+ /**
+ * Returns an option for detecting content type. If this option is used, the content type is
+ * detected from the blob name if not explicitly set. This option is on the client side only, it
+ * does not appear in a RPC call.
+ */
+ public static BlobTargetOption detectContentType() {
+ return new BlobTargetOption(StorageRpc.Option.DETECT_CONTENT_TYPE, true);
+ }
+
/**
* Returns an option to set a customer-supplied AES256 key for server-side encryption of the
* blob.
@@ -593,6 +602,7 @@ enum Option {
CUSTOMER_SUPPLIED_KEY,
KMS_KEY_NAME,
USER_PROJECT,
+ DETECT_CONTENT_TYPE,
IF_DISABLE_GZIP_CONTENT;
StorageRpc.Option toRpcOption() {
@@ -733,6 +743,15 @@ public static BlobWriteOption userProject(String userProject) {
public static BlobWriteOption disableGzipContent() {
return new BlobWriteOption(Option.IF_DISABLE_GZIP_CONTENT, true);
}
+
+ /**
+ * Returns an option for detecting content type. If this option is used, the content type is
+ * detected from the blob name if not explicitly set. This option is on the client side only, it
+ * does not appear in a RPC call.
+ */
+ public static BlobWriteOption detectContentType() {
+ return new BlobWriteOption(Option.DETECT_CONTENT_TYPE, true);
+ }
}
/** Class for specifying blob source options. */
@@ -1032,6 +1051,28 @@ public static BlobListOption delimiter(String delimiter) {
return new BlobListOption(StorageRpc.Option.DELIMITER, delimiter);
}
+ /**
+ * Returns an option to set a startOffset to filter results to objects whose names are
+ * lexicographically equal to or after startOffset. If endOffset is also set, the objects listed
+ * have names between startOffset (inclusive) and endOffset (exclusive).
+ *
+ * @param startOffset startOffset to filter the results
+ */
+ public static BlobListOption startOffset(String startOffset) {
+ return new BlobListOption(StorageRpc.Option.START_OFF_SET, startOffset);
+ }
+
+ /**
+ * Returns an option to set a endOffset to filter results to objects whose names are
+ * lexicographically before endOffset. If startOffset is also set, the objects listed have names
+ * between startOffset (inclusive) and endOffset (exclusive).
+ *
+ * @param endOffset endOffset to filter the results
+ */
+ public static BlobListOption endOffset(String endOffset) {
+ return new BlobListOption(StorageRpc.Option.END_OFF_SET, endOffset);
+ }
+
/**
* Returns an option to define the billing user project. This option is required by buckets with
* `requester_pays` flag enabled to assign operation costs.
@@ -1832,9 +1873,10 @@ public static Builder newBuilder() {
* Creates a new blob. Direct upload is used to upload {@code content}. For large content, {@link
* #writer} is recommended as it uses resumable upload. MD5 and CRC32C hashes of {@code content}
* are computed and used for validating transferred data. Accepts an optional userProject {@link
- * BlobGetOption} option which defines the project id to assign operational costs.
+ * BlobGetOption} option which defines the project id to assign operational costs. The content
+ * type is detected from the blob name if not explicitly set.
*
- *
Example of creating a blob from a byte array.
+ *
Example of creating a blob from a byte array:
*
*
{@code
* String bucketName = "my-unique-bucket";
@@ -1857,7 +1899,7 @@ public static Builder newBuilder() {
* Accepts a userProject {@link BlobGetOption} option, which defines the project id to assign
* operational costs.
*
- *
Example of creating a blob from a byte array.
+ *
Example of creating a blob from a byte array:
*
*
{@code
* String bucketName = "my-unique-bucket";
@@ -1876,7 +1918,7 @@ Blob create(
/**
* Creates a new blob. Direct upload is used to upload {@code content}. For large content, {@link
- * #writer} is recommended as it uses resumable upload. By default any md5 and crc32c values in
+ * #writer} is recommended as it uses resumable upload. By default any MD5 and CRC32C values in
* the given {@code blobInfo} are ignored unless requested via the {@code
* BlobWriteOption.md5Match} and {@code BlobWriteOption.crc32cMatch} options. The given input
* stream is closed upon success.
@@ -2603,11 +2645,11 @@ Blob createFrom(
ReadChannel reader(BlobId blob, BlobSourceOption... options);
/**
- * Creates a blob and return a channel for writing its content. By default any md5 and crc32c
+ * Creates a blob and returns a channel for writing its content. By default any MD5 and CRC32C
* values in the given {@code blobInfo} are ignored unless requested via the {@code
* BlobWriteOption.md5Match} and {@code BlobWriteOption.crc32cMatch} options.
*
- *
Example of writing a blob's content through a writer.
+ *
Example of writing a blob's content through a writer:
*
*