();
+
+ public ClientOptions build() {
+ ClientOptions __instance__ =
+ new ClientOptions(
+ source,
+ requirePrivilegedSourcePort,
+ access,
+ identitySquash,
+ anonymousUid,
+ anonymousGid);
+ __instance__.__explicitlySet__.addAll(__explicitlySet__);
+ return __instance__;
+ }
+
+ @com.fasterxml.jackson.annotation.JsonIgnore
+ public Builder copy(ClientOptions o) {
+ Builder copiedBuilder =
+ source(o.getSource())
+ .requirePrivilegedSourcePort(o.getRequirePrivilegedSourcePort())
+ .access(o.getAccess())
+ .identitySquash(o.getIdentitySquash())
+ .anonymousUid(o.getAnonymousUid())
+ .anonymousGid(o.getAnonymousGid());
+
+ copiedBuilder.__explicitlySet__.retainAll(o.__explicitlySet__);
+ return copiedBuilder;
+ }
+ }
+
+ /**
+ * Create a new builder.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Clients these options should apply to. Must be a either
+ * single IPv4 address or single IPv4 CIDR block.
+ *
+ **Note:** Access will also be limited by any applicable VCN
+ * security rules and the ability to route IP packets to the
+ * mount target. Mount targets do not have Internet-routable IP addresses.
+ *
+ **/
+ @com.fasterxml.jackson.annotation.JsonProperty("source")
+ String source;
+
+ /**
+ * If `true`, clients accessing the file system through this
+ * export must connect from a privileged source port. If
+ * unspecified, defaults to `true`.
+ *
+ **/
+ @com.fasterxml.jackson.annotation.JsonProperty("requirePrivilegedSourcePort")
+ Boolean requirePrivilegedSourcePort;
+ /**
+ * Type of access to grant clients using the file system
+ * through this export. If unspecified defaults to `READ_ONLY`.
+ *
+ **/
+ @lombok.extern.slf4j.Slf4j
+ public enum Access {
+ ReadWrite("READ_WRITE"),
+ ReadOnly("READ_ONLY"),
+
+ /**
+ * This value is used if a service returns a value for this enum that is not recognized by this
+ * version of the SDK.
+ */
+ UnknownEnumValue(null);
+
+ private final String value;
+ private static java.util.Map map;
+
+ static {
+ map = new java.util.HashMap<>();
+ for (Access v : Access.values()) {
+ if (v != UnknownEnumValue) {
+ map.put(v.getValue(), v);
+ }
+ }
+ }
+
+ Access(String value) {
+ this.value = value;
+ }
+
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() {
+ return value;
+ }
+
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static Access create(String key) {
+ if (map.containsKey(key)) {
+ return map.get(key);
+ }
+ LOG.warn(
+ "Received unknown value '{}' for enum 'Access', returning UnknownEnumValue",
+ key);
+ return UnknownEnumValue;
+ }
+ };
+ /**
+ * Type of access to grant clients using the file system
+ * through this export. If unspecified defaults to `READ_ONLY`.
+ *
+ **/
+ @com.fasterxml.jackson.annotation.JsonProperty("access")
+ Access access;
+ /**
+ * Used when clients accessing the file system through this export
+ * have their UID and GID remapped to 'anonymousUid' and
+ * 'anonymousGid'. If `ALL`, all users and groups are remapped;
+ * if `ROOT`, only the root user and group (UID/GID 0) are
+ * remapped; if `NONE`, no remapping is done. If unspecified,
+ * defaults to `ROOT`.
+ *
+ **/
+ @lombok.extern.slf4j.Slf4j
+ public enum IdentitySquash {
+ None("NONE"),
+ Root("ROOT"),
+ All("ALL"),
+
+ /**
+ * This value is used if a service returns a value for this enum that is not recognized by this
+ * version of the SDK.
+ */
+ UnknownEnumValue(null);
+
+ private final String value;
+ private static java.util.Map map;
+
+ static {
+ map = new java.util.HashMap<>();
+ for (IdentitySquash v : IdentitySquash.values()) {
+ if (v != UnknownEnumValue) {
+ map.put(v.getValue(), v);
+ }
+ }
+ }
+
+ IdentitySquash(String value) {
+ this.value = value;
+ }
+
+ @com.fasterxml.jackson.annotation.JsonValue
+ public String getValue() {
+ return value;
+ }
+
+ @com.fasterxml.jackson.annotation.JsonCreator
+ public static IdentitySquash create(String key) {
+ if (map.containsKey(key)) {
+ return map.get(key);
+ }
+ LOG.warn(
+ "Received unknown value '{}' for enum 'IdentitySquash', returning UnknownEnumValue",
+ key);
+ return UnknownEnumValue;
+ }
+ };
+ /**
+ * Used when clients accessing the file system through this export
+ * have their UID and GID remapped to 'anonymousUid' and
+ * 'anonymousGid'. If `ALL`, all users and groups are remapped;
+ * if `ROOT`, only the root user and group (UID/GID 0) are
+ * remapped; if `NONE`, no remapping is done. If unspecified,
+ * defaults to `ROOT`.
+ *
+ **/
+ @com.fasterxml.jackson.annotation.JsonProperty("identitySquash")
+ IdentitySquash identitySquash;
+
+ /**
+ * UID value to remap to when squashing a client UID (see
+ * identitySquash for more details.) If unspecified, defaults
+ * to `65534`.
+ *
+ **/
+ @com.fasterxml.jackson.annotation.JsonProperty("anonymousUid")
+ Long anonymousUid;
+
+ /**
+ * GID value to remap to when squashing a client GID (see
+ * identitySquash for more details.) If unspecified defaults
+ * to `65534`.
+ *
+ **/
+ @com.fasterxml.jackson.annotation.JsonProperty("anonymousGid")
+ Long anonymousGid;
+
+ @com.fasterxml.jackson.annotation.JsonIgnore
+ private final java.util.Set __explicitlySet__ = new java.util.HashSet();
+}
diff --git a/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/CreateExportDetails.java b/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/CreateExportDetails.java
index b51b3dcb40b..ecdb1f90437 100644
--- a/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/CreateExportDetails.java
+++ b/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/CreateExportDetails.java
@@ -23,6 +23,15 @@ public class CreateExportDetails {
@com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "")
@lombok.experimental.Accessors(fluent = true)
public static class Builder {
+ @com.fasterxml.jackson.annotation.JsonProperty("exportOptions")
+ private java.util.List exportOptions;
+
+ public Builder exportOptions(java.util.List exportOptions) {
+ this.exportOptions = exportOptions;
+ this.__explicitlySet__.add("exportOptions");
+ return this;
+ }
+
@com.fasterxml.jackson.annotation.JsonProperty("exportSetId")
private String exportSetId;
@@ -55,7 +64,7 @@ public Builder path(String path) {
public CreateExportDetails build() {
CreateExportDetails __instance__ =
- new CreateExportDetails(exportSetId, fileSystemId, path);
+ new CreateExportDetails(exportOptions, exportSetId, fileSystemId, path);
__instance__.__explicitlySet__.addAll(__explicitlySet__);
return __instance__;
}
@@ -63,7 +72,8 @@ public CreateExportDetails build() {
@com.fasterxml.jackson.annotation.JsonIgnore
public Builder copy(CreateExportDetails o) {
Builder copiedBuilder =
- exportSetId(o.getExportSetId())
+ exportOptions(o.getExportOptions())
+ .exportSetId(o.getExportSetId())
.fileSystemId(o.getFileSystemId())
.path(o.getPath());
@@ -79,6 +89,34 @@ public static Builder builder() {
return new Builder();
}
+ /**
+ * Export options for the new export. If left unspecified,
+ * defaults to:
+ *
+ * [
+ * {
+ * \"source\" : \"0.0.0.0/0\",
+ * \"requirePrivilegedSourcePort\" : false,
+ * \"access\" : \"READ_WRITE\",
+ * \"identitySquash\" : \"NONE\"
+ * }
+ * ]
+ *
+ **Note:** Mount targets do not have Internet-routable IP
+ * addresses. Therefore they will not be reachable from the
+ * Internet, even if an associated `ClientOptions` item has
+ * a source of `0.0.0.0/0`.
+ *
+ **If set to the empty array then the export will not be
+ * visible to any clients.**
+ *
+ * The export's `exportOptions` can be changed after creation
+ * using the `UpdateExport` operation.
+ *
+ **/
+ @com.fasterxml.jackson.annotation.JsonProperty("exportOptions")
+ java.util.List exportOptions;
+
/**
* The OCID of this export's export set.
**/
diff --git a/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/CreateSnapshotDetails.java b/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/CreateSnapshotDetails.java
index aaddded78c5..f53340c6023 100644
--- a/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/CreateSnapshotDetails.java
+++ b/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/CreateSnapshotDetails.java
@@ -67,7 +67,7 @@ public static Builder builder() {
}
/**
- * The OCID of this export's file system.
+ * The OCID of the file system to take a snapshot of.
**/
@com.fasterxml.jackson.annotation.JsonProperty("fileSystemId")
String fileSystemId;
diff --git a/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/Export.java b/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/Export.java
index cd6713f8659..42a680d0b16 100644
--- a/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/Export.java
+++ b/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/Export.java
@@ -34,6 +34,9 @@
*
* No two non-'DELETED' export resources in the same export set can
* reference the same file system.
+ *
+ * Use `exportOptions` to control access to an export. For more information, see
+ * [Export Options](https://docs.us-phoenix-1.oraclecloud.com/Content/File/Tasks/exportoptions.htm).
*
*
* Note: This model distinguishes fields that are {@code null} because they are unset from fields that are explicitly
@@ -51,6 +54,15 @@ public class Export {
@com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "")
@lombok.experimental.Accessors(fluent = true)
public static class Builder {
+ @com.fasterxml.jackson.annotation.JsonProperty("exportOptions")
+ private java.util.List exportOptions;
+
+ public Builder exportOptions(java.util.List exportOptions) {
+ this.exportOptions = exportOptions;
+ this.__explicitlySet__.add("exportOptions");
+ return this;
+ }
+
@com.fasterxml.jackson.annotation.JsonProperty("exportSetId")
private String exportSetId;
@@ -110,7 +122,14 @@ public Builder timeCreated(java.util.Date timeCreated) {
public Export build() {
Export __instance__ =
- new Export(exportSetId, fileSystemId, id, lifecycleState, path, timeCreated);
+ new Export(
+ exportOptions,
+ exportSetId,
+ fileSystemId,
+ id,
+ lifecycleState,
+ path,
+ timeCreated);
__instance__.__explicitlySet__.addAll(__explicitlySet__);
return __instance__;
}
@@ -118,7 +137,8 @@ public Export build() {
@com.fasterxml.jackson.annotation.JsonIgnore
public Builder copy(Export o) {
Builder copiedBuilder =
- exportSetId(o.getExportSetId())
+ exportOptions(o.getExportOptions())
+ .exportSetId(o.getExportSetId())
.fileSystemId(o.getFileSystemId())
.id(o.getId())
.lifecycleState(o.getLifecycleState())
@@ -137,6 +157,37 @@ public static Builder builder() {
return new Builder();
}
+ /**
+ * Policies that apply to NFS requests made through this
+ * export. `exportOptions` contains a sequential list of
+ * `ClientOptions`. Each `ClientOptions` item defines the
+ * export options that are applied to a specified
+ * set of clients.
+ *
+ * For each NFS request, the first `ClientOptions` option
+ * in the list whose `source` attribute matches the source
+ * IP address of the request is applied.
+ *
+ * If a client source IP address does not match the `source`
+ * property of any `ClientOptions` in the list, then the
+ * export will be invisible to that client. This export will
+ * not be returned by `MOUNTPROC_EXPORT` calls made by the client
+ * and any attempt to mount or access the file system through
+ * this export will result in an error.
+ *
+ **Exports without defined `ClientOptions` are invisible to all clients.**
+ *
+ * If one export is invisible to a particular client, associated file
+ * systems may still be accessible through other exports on the same
+ * or different mount targets.
+ * To completely deny client access to a file system, be sure that the client
+ * source IP address is not included in any export for any mount target
+ * associated with the file system.
+ *
+ **/
+ @com.fasterxml.jackson.annotation.JsonProperty("exportOptions")
+ java.util.List exportOptions;
+
/**
* The OCID of this export's export set.
**/
diff --git a/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/ExportSet.java b/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/ExportSet.java
index 72c2af01333..2701e0cad08 100644
--- a/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/ExportSet.java
+++ b/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/ExportSet.java
@@ -249,7 +249,7 @@ public static LifecycleState create(String key) {
Long maxFsStatBytes;
/**
- * Controls the maximum `ffiles`, `ffiles`, and `afiles`
+ * Controls the maximum `tfiles`, `ffiles`, and `afiles`
* values reported by `NFS FSSTAT` calls through any associated
* mount targets. This is an advanced feature. For most
* applications, use the default value. The
diff --git a/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/UpdateExportDetails.java b/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/UpdateExportDetails.java
new file mode 100644
index 00000000000..bfd8b9c420d
--- /dev/null
+++ b/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/model/UpdateExportDetails.java
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ */
+package com.oracle.bmc.filestorage.model;
+
+/**
+ *
+ *
+ * Note: This model distinguishes fields that are {@code null} because they are unset from fields that are explicitly
+ * set to {@code null}. This is done in the setter methods of the {@link Builder}, which maintain a set of all
+ * explicitly set fields called {@link #__explicitlySet__}. The {@link #hashCode()} and {@link #equals(Object)} methods
+ * are implemented to take {@link #__explicitlySet__} into account. The constructor, on the other hand, does not
+ * set {@link #__explicitlySet__} (since the constructor cannot distinguish explicit {@code null} from unset
+ * {@code null}). As a consequence, objects should always be created or deserialized using the {@link Builder}.
+ **/
+@javax.annotation.Generated(value = "OracleSDKGenerator", comments = "API Version: 20171215")
+@lombok.Value
+@com.fasterxml.jackson.databind.annotation.JsonDeserialize(
+ builder = UpdateExportDetails.Builder.class
+)
+@com.fasterxml.jackson.annotation.JsonFilter(com.oracle.bmc.http.internal.ExplicitlySetFilter.NAME)
+public class UpdateExportDetails {
+ @com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "")
+ @lombok.experimental.Accessors(fluent = true)
+ public static class Builder {
+ @com.fasterxml.jackson.annotation.JsonProperty("exportOptions")
+ private java.util.List exportOptions;
+
+ public Builder exportOptions(java.util.List exportOptions) {
+ this.exportOptions = exportOptions;
+ this.__explicitlySet__.add("exportOptions");
+ return this;
+ }
+
+ @com.fasterxml.jackson.annotation.JsonIgnore
+ private final java.util.Set __explicitlySet__ = new java.util.HashSet();
+
+ public UpdateExportDetails build() {
+ UpdateExportDetails __instance__ = new UpdateExportDetails(exportOptions);
+ __instance__.__explicitlySet__.addAll(__explicitlySet__);
+ return __instance__;
+ }
+
+ @com.fasterxml.jackson.annotation.JsonIgnore
+ public Builder copy(UpdateExportDetails o) {
+ Builder copiedBuilder = exportOptions(o.getExportOptions());
+
+ copiedBuilder.__explicitlySet__.retainAll(o.__explicitlySet__);
+ return copiedBuilder;
+ }
+ }
+
+ /**
+ * Create a new builder.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * New export options for the export.
+ *
+ **Setting to the empty array will make the export invisible to all clients.**
+ *
+ * Leaving unset will leave the `exportOptions` unchanged.
+ *
+ **/
+ @com.fasterxml.jackson.annotation.JsonProperty("exportOptions")
+ java.util.List exportOptions;
+
+ @com.fasterxml.jackson.annotation.JsonIgnore
+ private final java.util.Set __explicitlySet__ = new java.util.HashSet();
+}
diff --git a/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/requests/UpdateExportRequest.java b/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/requests/UpdateExportRequest.java
new file mode 100644
index 00000000000..aa6d2006b57
--- /dev/null
+++ b/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/requests/UpdateExportRequest.java
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ */
+package com.oracle.bmc.filestorage.requests;
+
+import com.oracle.bmc.filestorage.model.*;
+
+@javax.annotation.Generated(value = "OracleSDKGenerator", comments = "API Version: 20171215")
+@lombok.Builder(builderClassName = "Builder", buildMethodName = "buildWithoutInvocationCallback")
+@lombok.Getter
+public class UpdateExportRequest extends com.oracle.bmc.requests.BmcRequest {
+
+ /**
+ * The OCID of the export.
+ */
+ private String exportId;
+
+ /**
+ * Details object for updating an export.
+ */
+ private UpdateExportDetails updateExportDetails;
+
+ /**
+ * For optimistic concurrency control. In the PUT or DELETE call
+ * for a resource, set the `if-match` parameter to the value of the
+ * etag from a previous GET or POST response for that resource.
+ * The resource will be updated or deleted only if the etag you
+ * provide matches the resource's current etag value.
+ *
+ */
+ private String ifMatch;
+
+ public static class Builder {
+ private com.oracle.bmc.util.internal.Consumer
+ invocationCallback = null;
+
+ /**
+ * Set the invocation callback for the request to be built.
+ * @param invocationCallback the invocation callback to be set for the request
+ * @return this builder instance
+ */
+ public Builder invocationCallback(
+ com.oracle.bmc.util.internal.Consumer
+ invocationCallback) {
+ this.invocationCallback = invocationCallback;
+ return this;
+ }
+
+ /**
+ * Copy method to populate the builder with values from the given instance.
+ * @return this builder instance
+ */
+ public Builder copy(UpdateExportRequest o) {
+ exportId(o.getExportId());
+ updateExportDetails(o.getUpdateExportDetails());
+ ifMatch(o.getIfMatch());
+ invocationCallback(o.getInvocationCallback());
+ return this;
+ }
+
+ /**
+ * Build the instance of UpdateExportRequest as configured by this builder
+ *
+ * Note that this method takes calls to {@link Builder#invocationCallback(com.oracle.bmc.util.internal.Consumer)} into account,
+ * while the method {@link Builder#buildWithoutInvocationCallback} does not.
+ *
+ * This is the preferred method to build an instance.
+ *
+ * @return instance of UpdateExportRequest
+ */
+ public UpdateExportRequest build() {
+ UpdateExportRequest request = buildWithoutInvocationCallback();
+ request.setInvocationCallback(invocationCallback);
+ return request;
+ }
+ }
+}
diff --git a/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/responses/UpdateExportResponse.java b/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/responses/UpdateExportResponse.java
new file mode 100644
index 00000000000..bc2d841fefe
--- /dev/null
+++ b/bmc-filestorage/src/main/java/com/oracle/bmc/filestorage/responses/UpdateExportResponse.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ */
+package com.oracle.bmc.filestorage.responses;
+
+import com.oracle.bmc.filestorage.model.*;
+
+@javax.annotation.Generated(value = "OracleSDKGenerator", comments = "API Version: 20171215")
+@lombok.Builder(builderClassName = "Builder")
+@lombok.Getter
+public class UpdateExportResponse {
+
+ /**
+ * For optimistic concurrency control. See `if-match`.
+ */
+ private String etag;
+
+ /**
+ * Unique Oracle-assigned identifier for the request. If
+ * you need to contact Oracle about a particular request,
+ * please provide the request ID.
+ *
+ */
+ private String opcRequestId;
+
+ /**
+ * The returned Export instance.
+ */
+ private Export export;
+
+ public static class Builder {
+ /**
+ * Copy method to populate the builder with values from the given instance.
+ * @return this builder instance
+ */
+ public Builder copy(UpdateExportResponse o) {
+ etag(o.getEtag());
+ opcRequestId(o.getOpcRequestId());
+ export(o.getExport());
+
+ return this;
+ }
+ }
+}
diff --git a/bmc-full/pom.xml b/bmc-full/pom.xml
index 37d011bc39a..d4df9381848 100644
--- a/bmc-full/pom.xml
+++ b/bmc-full/pom.xml
@@ -4,7 +4,7 @@
com.oracle.oci.sdk
oci-java-sdk
- 1.2.41
+ 1.2.42
../pom.xml
oci-java-sdk-full
@@ -16,7 +16,7 @@
com.oracle.oci.sdk
oci-java-sdk-bom
- 1.2.41
+ 1.2.42
pom
import
diff --git a/bmc-identity/pom.xml b/bmc-identity/pom.xml
index db8755249ae..4dd8fe3d20a 100644
--- a/bmc-identity/pom.xml
+++ b/bmc-identity/pom.xml
@@ -5,7 +5,7 @@
com.oracle.oci.sdk
oci-java-sdk
- 1.2.41
+ 1.2.42
../pom.xml
@@ -18,7 +18,7 @@
com.oracle.oci.sdk
oci-java-sdk-common
- 1.2.41
+ 1.2.42
diff --git a/bmc-loadbalancer/pom.xml b/bmc-loadbalancer/pom.xml
index 01751757e88..7e3c9a2f3ce 100644
--- a/bmc-loadbalancer/pom.xml
+++ b/bmc-loadbalancer/pom.xml
@@ -5,7 +5,7 @@
com.oracle.oci.sdk
oci-java-sdk
- 1.2.41
+ 1.2.42
../pom.xml
@@ -18,7 +18,7 @@
com.oracle.oci.sdk
oci-java-sdk-common
- 1.2.41
+ 1.2.42
diff --git a/bmc-loadbalancer/src/main/java/com/oracle/bmc/loadbalancer/model/CreateLoadBalancerDetails.java b/bmc-loadbalancer/src/main/java/com/oracle/bmc/loadbalancer/model/CreateLoadBalancerDetails.java
index 7a3c3042002..8ee78209ed4 100644
--- a/bmc-loadbalancer/src/main/java/com/oracle/bmc/loadbalancer/model/CreateLoadBalancerDetails.java
+++ b/bmc-loadbalancer/src/main/java/com/oracle/bmc/loadbalancer/model/CreateLoadBalancerDetails.java
@@ -50,6 +50,16 @@ public Builder compartmentId(String compartmentId) {
return this;
}
+ @com.fasterxml.jackson.annotation.JsonProperty("definedTags")
+ private java.util.Map> definedTags;
+
+ public Builder definedTags(
+ java.util.Map> definedTags) {
+ this.definedTags = definedTags;
+ this.__explicitlySet__.add("definedTags");
+ return this;
+ }
+
@com.fasterxml.jackson.annotation.JsonProperty("displayName")
private String displayName;
@@ -59,6 +69,15 @@ public Builder displayName(String displayName) {
return this;
}
+ @com.fasterxml.jackson.annotation.JsonProperty("freeformTags")
+ private java.util.Map freeformTags;
+
+ public Builder freeformTags(java.util.Map freeformTags) {
+ this.freeformTags = freeformTags;
+ this.__explicitlySet__.add("freeformTags");
+ return this;
+ }
+
@com.fasterxml.jackson.annotation.JsonProperty("hostnames")
private java.util.Map hostnames;
@@ -122,7 +141,9 @@ public CreateLoadBalancerDetails build() {
backendSets,
certificates,
compartmentId,
+ definedTags,
displayName,
+ freeformTags,
hostnames,
isPrivate,
listeners,
@@ -139,7 +160,9 @@ public Builder copy(CreateLoadBalancerDetails o) {
backendSets(o.getBackendSets())
.certificates(o.getCertificates())
.compartmentId(o.getCompartmentId())
+ .definedTags(o.getDefinedTags())
.displayName(o.getDisplayName())
+ .freeformTags(o.getFreeformTags())
.hostnames(o.getHostnames())
.isPrivate(o.getIsPrivate())
.listeners(o.getListeners())
@@ -171,6 +194,16 @@ public static Builder builder() {
@com.fasterxml.jackson.annotation.JsonProperty("compartmentId")
String compartmentId;
+ /**
+ * Defined tags for this resource. Each key is predefined and scoped to a namespace.
+ * For more information, see [Resource Tags](https://docs.us-phoenix-1.oraclecloud.com/Content/General/Concepts/resourcetags.htm).
+ *
+ * Example: `{\"Operations\": {\"CostCenter\": \"42\"}}`
+ *
+ **/
+ @com.fasterxml.jackson.annotation.JsonProperty("definedTags")
+ java.util.Map> definedTags;
+
/**
* A user-friendly name. It does not have to be unique, and it is changeable.
* Avoid entering confidential information.
@@ -181,6 +214,16 @@ public static Builder builder() {
@com.fasterxml.jackson.annotation.JsonProperty("displayName")
String displayName;
+ /**
+ * Free-form tags for this resource. Each tag is a simple key-value pair with no predefined name, type, or namespace.
+ * For more information, see [Resource Tags](https://docs.us-phoenix-1.oraclecloud.com/Content/General/Concepts/resourcetags.htm).
+ *
+ * Example: `{\"Department\": \"Finance\"}`
+ *
+ **/
+ @com.fasterxml.jackson.annotation.JsonProperty("freeformTags")
+ java.util.Map freeformTags;
+
@com.fasterxml.jackson.annotation.JsonProperty("hostnames")
java.util.Map hostnames;
diff --git a/bmc-loadbalancer/src/main/java/com/oracle/bmc/loadbalancer/model/LoadBalancer.java b/bmc-loadbalancer/src/main/java/com/oracle/bmc/loadbalancer/model/LoadBalancer.java
index 8328a16bcb7..da5c3cdf6e3 100644
--- a/bmc-loadbalancer/src/main/java/com/oracle/bmc/loadbalancer/model/LoadBalancer.java
+++ b/bmc-loadbalancer/src/main/java/com/oracle/bmc/loadbalancer/model/LoadBalancer.java
@@ -58,6 +58,16 @@ public Builder compartmentId(String compartmentId) {
return this;
}
+ @com.fasterxml.jackson.annotation.JsonProperty("definedTags")
+ private java.util.Map> definedTags;
+
+ public Builder definedTags(
+ java.util.Map> definedTags) {
+ this.definedTags = definedTags;
+ this.__explicitlySet__.add("definedTags");
+ return this;
+ }
+
@com.fasterxml.jackson.annotation.JsonProperty("displayName")
private String displayName;
@@ -67,6 +77,15 @@ public Builder displayName(String displayName) {
return this;
}
+ @com.fasterxml.jackson.annotation.JsonProperty("freeformTags")
+ private java.util.Map freeformTags;
+
+ public Builder freeformTags(java.util.Map freeformTags) {
+ this.freeformTags = freeformTags;
+ this.__explicitlySet__.add("freeformTags");
+ return this;
+ }
+
@com.fasterxml.jackson.annotation.JsonProperty("hostnames")
private java.util.Map hostnames;
@@ -166,7 +185,9 @@ public LoadBalancer build() {
backendSets,
certificates,
compartmentId,
+ definedTags,
displayName,
+ freeformTags,
hostnames,
id,
ipAddresses,
@@ -187,7 +208,9 @@ public Builder copy(LoadBalancer o) {
backendSets(o.getBackendSets())
.certificates(o.getCertificates())
.compartmentId(o.getCompartmentId())
+ .definedTags(o.getDefinedTags())
.displayName(o.getDisplayName())
+ .freeformTags(o.getFreeformTags())
.hostnames(o.getHostnames())
.id(o.getId())
.ipAddresses(o.getIpAddresses())
@@ -223,6 +246,16 @@ public static Builder builder() {
@com.fasterxml.jackson.annotation.JsonProperty("compartmentId")
String compartmentId;
+ /**
+ * Defined tags for this resource. Each key is predefined and scoped to a namespace.
+ * For more information, see [Resource Tags](https://docs.us-phoenix-1.oraclecloud.com/Content/General/Concepts/resourcetags.htm).
+ *
+ * Example: `{\"Operations\": {\"CostCenter\": \"42\"}}`
+ *
+ **/
+ @com.fasterxml.jackson.annotation.JsonProperty("definedTags")
+ java.util.Map> definedTags;
+
/**
* A user-friendly name. It does not have to be unique, and it is changeable.
*
@@ -232,6 +265,16 @@ public static Builder builder() {
@com.fasterxml.jackson.annotation.JsonProperty("displayName")
String displayName;
+ /**
+ * Free-form tags for this resource. Each tag is a simple key-value pair with no predefined name, type, or namespace.
+ * For more information, see [Resource Tags](https://docs.us-phoenix-1.oraclecloud.com/Content/General/Concepts/resourcetags.htm).
+ *
+ * Example: `{\"Department\": \"Finance\"}`
+ *
+ **/
+ @com.fasterxml.jackson.annotation.JsonProperty("freeformTags")
+ java.util.Map freeformTags;
+
@com.fasterxml.jackson.annotation.JsonProperty("hostnames")
java.util.Map hostnames;
diff --git a/bmc-loadbalancer/src/main/java/com/oracle/bmc/loadbalancer/model/UpdateLoadBalancerDetails.java b/bmc-loadbalancer/src/main/java/com/oracle/bmc/loadbalancer/model/UpdateLoadBalancerDetails.java
index 38635c3a8bc..e4311fdddda 100644
--- a/bmc-loadbalancer/src/main/java/com/oracle/bmc/loadbalancer/model/UpdateLoadBalancerDetails.java
+++ b/bmc-loadbalancer/src/main/java/com/oracle/bmc/loadbalancer/model/UpdateLoadBalancerDetails.java
@@ -23,6 +23,16 @@ public class UpdateLoadBalancerDetails {
@com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "")
@lombok.experimental.Accessors(fluent = true)
public static class Builder {
+ @com.fasterxml.jackson.annotation.JsonProperty("definedTags")
+ private java.util.Map> definedTags;
+
+ public Builder definedTags(
+ java.util.Map> definedTags) {
+ this.definedTags = definedTags;
+ this.__explicitlySet__.add("definedTags");
+ return this;
+ }
+
@com.fasterxml.jackson.annotation.JsonProperty("displayName")
private String displayName;
@@ -32,18 +42,31 @@ public Builder displayName(String displayName) {
return this;
}
+ @com.fasterxml.jackson.annotation.JsonProperty("freeformTags")
+ private java.util.Map freeformTags;
+
+ public Builder freeformTags(java.util.Map freeformTags) {
+ this.freeformTags = freeformTags;
+ this.__explicitlySet__.add("freeformTags");
+ return this;
+ }
+
@com.fasterxml.jackson.annotation.JsonIgnore
private final java.util.Set __explicitlySet__ = new java.util.HashSet();
public UpdateLoadBalancerDetails build() {
- UpdateLoadBalancerDetails __instance__ = new UpdateLoadBalancerDetails(displayName);
+ UpdateLoadBalancerDetails __instance__ =
+ new UpdateLoadBalancerDetails(definedTags, displayName, freeformTags);
__instance__.__explicitlySet__.addAll(__explicitlySet__);
return __instance__;
}
@com.fasterxml.jackson.annotation.JsonIgnore
public Builder copy(UpdateLoadBalancerDetails o) {
- Builder copiedBuilder = displayName(o.getDisplayName());
+ Builder copiedBuilder =
+ definedTags(o.getDefinedTags())
+ .displayName(o.getDisplayName())
+ .freeformTags(o.getFreeformTags());
copiedBuilder.__explicitlySet__.retainAll(o.__explicitlySet__);
return copiedBuilder;
@@ -57,6 +80,16 @@ public static Builder builder() {
return new Builder();
}
+ /**
+ * Defined tags for this resource. Each key is predefined and scoped to a namespace.
+ * For more information, see [Resource Tags](https://docs.us-phoenix-1.oraclecloud.com/Content/General/Concepts/resourcetags.htm).
+ *
+ * Example: `{\"Operations\": {\"CostCenter\": \"42\"}}`
+ *
+ **/
+ @com.fasterxml.jackson.annotation.JsonProperty("definedTags")
+ java.util.Map> definedTags;
+
/**
* The user-friendly display name for the load balancer. It does not have to be unique, and it is changeable.
* Avoid entering confidential information.
@@ -67,6 +100,16 @@ public static Builder builder() {
@com.fasterxml.jackson.annotation.JsonProperty("displayName")
String displayName;
+ /**
+ * Free-form tags for this resource. Each tag is a simple key-value pair with no predefined name, type, or namespace.
+ * For more information, see [Resource Tags](https://docs.us-phoenix-1.oraclecloud.com/Content/General/Concepts/resourcetags.htm).
+ *
+ * Example: `{\"Department\": \"Finance\"}`
+ *
+ **/
+ @com.fasterxml.jackson.annotation.JsonProperty("freeformTags")
+ java.util.Map freeformTags;
+
@com.fasterxml.jackson.annotation.JsonIgnore
private final java.util.Set __explicitlySet__ = new java.util.HashSet();
}
diff --git a/bmc-objectstorage/bmc-objectstorage-extensions/pom.xml b/bmc-objectstorage/bmc-objectstorage-extensions/pom.xml
index 72e506ec4cf..c6a3df42ea1 100644
--- a/bmc-objectstorage/bmc-objectstorage-extensions/pom.xml
+++ b/bmc-objectstorage/bmc-objectstorage-extensions/pom.xml
@@ -5,7 +5,7 @@
com.oracle.oci.sdk
oci-java-sdk
- 1.2.41
+ 1.2.42
../../pom.xml
@@ -18,12 +18,12 @@
com.oracle.oci.sdk
oci-java-sdk-common
- 1.2.41
+ 1.2.42
com.oracle.oci.sdk
oci-java-sdk-objectstorage-generated
- 1.2.41
+ 1.2.42
diff --git a/bmc-objectstorage/bmc-objectstorage-extensions/src/main/java/com/oracle/bmc/objectstorage/transfer/ProgressReporter.java b/bmc-objectstorage/bmc-objectstorage-extensions/src/main/java/com/oracle/bmc/objectstorage/transfer/ProgressReporter.java
new file mode 100644
index 00000000000..4f0c22ca0f4
--- /dev/null
+++ b/bmc-objectstorage/bmc-objectstorage-extensions/src/main/java/com/oracle/bmc/objectstorage/transfer/ProgressReporter.java
@@ -0,0 +1,14 @@
+/**
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ */
+package com.oracle.bmc.objectstorage.transfer;
+
+public interface ProgressReporter {
+ /**
+ * Notification to indicate that progress was made during an upload operation.
+ *
+ * @param completed The number of bytes that have been already been uploaded for this operation.
+ * @param total The total number of bytes to upload for this operation.
+ */
+ void onProgress(final long completed, final long total);
+}
diff --git a/bmc-objectstorage/bmc-objectstorage-extensions/src/main/java/com/oracle/bmc/objectstorage/transfer/ProgressTracker.java b/bmc-objectstorage/bmc-objectstorage-extensions/src/main/java/com/oracle/bmc/objectstorage/transfer/ProgressTracker.java
new file mode 100644
index 00000000000..ba39515f0c5
--- /dev/null
+++ b/bmc-objectstorage/bmc-objectstorage-extensions/src/main/java/com/oracle/bmc/objectstorage/transfer/ProgressTracker.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ */
+package com.oracle.bmc.objectstorage.transfer;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+
+import javax.annotation.concurrent.NotThreadSafe;
+
+@NotThreadSafe
+@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
+class ProgressTracker {
+ @NonNull private final ProgressReporter progressReporter;
+ private final long totalBytes;
+
+ @Getter(value = AccessLevel.PROTECTED)
+ private long totalBytesRead = 0;
+
+ void onBytesRead(final long bytesRead) {
+ totalBytesRead += bytesRead;
+ if (totalBytesRead < 0 || totalBytesRead > totalBytes) {
+ throw new IllegalStateException(
+ String.format(
+ "IllegalState - bytes read %d causes total bytes read(%d)/total bytes(%d)",
+ bytesRead,
+ totalBytesRead,
+ totalBytes));
+ }
+ progressReporter.onProgress(totalBytesRead, totalBytes);
+ }
+
+ ProgressTracker reset() {
+ totalBytesRead = 0;
+ return this;
+ }
+
+ protected void invalidateBytesRead(long invalidByteCount) {
+ if (invalidByteCount > totalBytesRead) {
+ throw new IllegalStateException(
+ String.format(
+ "Cannot invalidate %d bytes when total read bytes is %d",
+ invalidByteCount,
+ totalBytesRead));
+ }
+ totalBytesRead -= invalidByteCount;
+ }
+}
diff --git a/bmc-objectstorage/bmc-objectstorage-extensions/src/main/java/com/oracle/bmc/objectstorage/transfer/ProgressTrackerFactory.java b/bmc-objectstorage/bmc-objectstorage-extensions/src/main/java/com/oracle/bmc/objectstorage/transfer/ProgressTrackerFactory.java
new file mode 100644
index 00000000000..59c9129afc4
--- /dev/null
+++ b/bmc-objectstorage/bmc-objectstorage-extensions/src/main/java/com/oracle/bmc/objectstorage/transfer/ProgressTrackerFactory.java
@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ */
+package com.oracle.bmc.objectstorage.transfer;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+abstract class ProgressTrackerFactory {
+ private static final ProgressTrackerFactory DUMMY_PROGRESS_TRACKER_FACTORY =
+ new ProgressTrackerFactory() {
+ @Override
+ ProgressTracker getProgressTracker() {
+ return null;
+ }
+ };
+
+ static ProgressTrackerFactory createSingleUploadProgressTrackerFactory(
+ final ProgressReporter progressReporter, final long totalBytes) {
+ if (progressReporter == null) {
+ return DUMMY_PROGRESS_TRACKER_FACTORY;
+ }
+ return new SingleUploadProgressTrackerFactory(progressReporter, totalBytes);
+ }
+
+ static ProgressTrackerFactory createMultiPartUploadProgressTrackerFactory(
+ final ProgressReporter progressReporter, final long totalBytes) {
+ if (progressReporter == null) {
+ return DUMMY_PROGRESS_TRACKER_FACTORY;
+ }
+ return new MultiPartUploadProgressTrackerFactory(progressReporter, totalBytes);
+ }
+
+ abstract ProgressTracker getProgressTracker();
+
+ private static class SingleUploadProgressTrackerFactory extends ProgressTrackerFactory {
+
+ // This progress tracker is implicitly thread safe by definition since there is only a single upload
+ // going on (in a single upload thread).
+ private final ProgressTracker progressTracker;
+
+ private SingleUploadProgressTrackerFactory(
+ final ProgressReporter progressReporter, final long totalBytes) {
+ progressTracker = new ProgressTracker(progressReporter, totalBytes);
+ }
+
+ @Override
+ ProgressTracker getProgressTracker() {
+ return progressTracker;
+ }
+ }
+
+ private static class MultiPartUploadProgressTrackerFactory extends ProgressTrackerFactory {
+ private static final ProgressReporter DUMMY_PROGRESS_REPORTER =
+ new ProgressReporter() {
+ @Override
+ public void onProgress(long completed, long total) {}
+ };
+ private final ProgressTracker rootProgressTracker;
+
+ private MultiPartUploadProgressTrackerFactory(
+ final ProgressReporter progressReporter, final long totalBytes) {
+ rootProgressTracker = new ProgressTracker(progressReporter, totalBytes);
+ }
+
+ @Override
+ ProgressTracker getProgressTracker() {
+ return new SubProgressTracker();
+ }
+
+ /**
+ * This progress tracker is threadsafe as updates to the aggregate root progress tracker is synchronized.
+ */
+ @ThreadSafe
+ private class SubProgressTracker extends ProgressTracker {
+ private SubProgressTracker() {
+ super(DUMMY_PROGRESS_REPORTER, Long.MAX_VALUE);
+ }
+
+ @Override
+ void onBytesRead(final long bytesRead) {
+ super.onBytesRead(bytesRead);
+ synchronized (rootProgressTracker) {
+ rootProgressTracker.onBytesRead(bytesRead);
+ }
+ }
+
+ @Override
+ ProgressTracker reset() {
+ synchronized (rootProgressTracker) {
+ rootProgressTracker.invalidateBytesRead(getTotalBytesRead());
+ }
+ return super.reset();
+ }
+ }
+ }
+}
diff --git a/bmc-objectstorage/bmc-objectstorage-extensions/src/main/java/com/oracle/bmc/objectstorage/transfer/ProgressTrackingInputStreamFactory.java b/bmc-objectstorage/bmc-objectstorage-extensions/src/main/java/com/oracle/bmc/objectstorage/transfer/ProgressTrackingInputStreamFactory.java
new file mode 100644
index 00000000000..bc5c35c7b13
--- /dev/null
+++ b/bmc-objectstorage/bmc-objectstorage-extensions/src/main/java/com/oracle/bmc/objectstorage/transfer/ProgressTrackingInputStreamFactory.java
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ */
+package com.oracle.bmc.objectstorage.transfer;
+
+import com.oracle.bmc.io.DuplicatableInputStream;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+class ProgressTrackingInputStreamFactory {
+ static InputStream create(final InputStream source, final ProgressTracker progressTracker) {
+ if (progressTracker == null) {
+ return source;
+ }
+ if (source instanceof DuplicatableInputStream) {
+ return new DuplicatableProgressTrackingInputStream(source, progressTracker);
+ }
+ return new ProgressTrackingInputStream(source, progressTracker);
+ }
+
+ @Slf4j
+ @RequiredArgsConstructor(access = AccessLevel.PROTECTED)
+ private static class ProgressTrackingInputStream extends InputStream {
+ @Getter(value = AccessLevel.PROTECTED)
+ private final InputStream source;
+
+ @NonNull
+ @Getter(value = AccessLevel.PROTECTED)
+ private final ProgressTracker progressTracker;
+
+ @Override
+ public long skip(long n) throws IOException {
+ return source.skip(n);
+ }
+
+ @Override
+ public int available() throws IOException {
+ return source.available();
+ }
+
+ @Override
+ public void mark(int readlimit) {
+ source.mark(readlimit);
+ }
+
+ @Override
+ public void reset() throws IOException {
+ source.reset();
+ }
+
+ @Override
+ public boolean markSupported() {
+ return source.markSupported();
+ }
+
+ @Override
+ public int read() throws IOException {
+ final int bytesRead = source.read();
+ checkAndReportBytesRead(bytesRead);
+ return bytesRead;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ final int bytesRead = source.read(b, off, len);
+ checkAndReportBytesRead(bytesRead);
+ return bytesRead;
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ final int bytesRead = source.read(b);
+ checkAndReportBytesRead(bytesRead);
+ return bytesRead;
+ }
+
+ @Override
+ public void close() throws IOException {
+ source.close();
+ }
+
+ private void checkAndReportBytesRead(final int bytesRead) {
+ if (bytesRead != -1) {
+ progressTracker.onBytesRead(bytesRead);
+ }
+ }
+ }
+
+ private final static class DuplicatableProgressTrackingInputStream
+ extends ProgressTrackingInputStream implements DuplicatableInputStream {
+
+ private DuplicatableProgressTrackingInputStream(
+ final InputStream source, final ProgressTracker progressTracker) {
+ super(source, progressTracker);
+
+ if (!(source instanceof DuplicatableInputStream)) {
+ throw new IllegalStateException("Source MUST be a DuplicatableInputStream");
+ }
+ }
+
+ /**
+ * The progress tracking input stream resulting from this call will re-use the progress tracker from the parent
+ * progress tracking input stream after resetting it, thus invalidating the progress tracked by the parent
+ * stream until now. To ensure correctness of the progress tracking functionality, do NOT read from the parent
+ * stream after duplicating from it.
+ * @return The duplicated progress tracking input stream.
+ */
+ @Override
+ public InputStream duplicate() {
+ return ProgressTrackingInputStreamFactory.create(
+ ((DuplicatableInputStream) getSource()).duplicate(),
+ getProgressTracker().reset());
+ }
+ }
+}
diff --git a/bmc-objectstorage/bmc-objectstorage-extensions/src/main/java/com/oracle/bmc/objectstorage/transfer/UploadManager.java b/bmc-objectstorage/bmc-objectstorage-extensions/src/main/java/com/oracle/bmc/objectstorage/transfer/UploadManager.java
index 71711bbcd30..d98241fce87 100644
--- a/bmc-objectstorage/bmc-objectstorage-extensions/src/main/java/com/oracle/bmc/objectstorage/transfer/UploadManager.java
+++ b/bmc-objectstorage/bmc-objectstorage-extensions/src/main/java/com/oracle/bmc/objectstorage/transfer/UploadManager.java
@@ -78,6 +78,9 @@ public UploadResponse upload(UploadRequest uploadDetails) {
private UploadResponse singleUpload(
UploadRequest uploadDetails, InputStream stream, long contentLength) {
+ final ProgressTrackerFactory progressTrackerFactory =
+ ProgressTrackerFactory.createSingleUploadProgressTrackerFactory(
+ uploadDetails.progressReporter, contentLength);
PutObjectRequest putObjectRequest = uploadDetails.putObjectRequest;
if (MultipartUtils.shouldCalculateMd5(uploadConfiguration, putObjectRequest)) {
MD5Calculation md5Calculation =
@@ -88,7 +91,19 @@ private UploadResponse singleUpload(
PutObjectRequest.builder()
.copy(putObjectRequest)
.contentMD5(md5Calculation.md5)
- .putObjectBody(md5Calculation.streamToUse)
+ .putObjectBody(
+ ProgressTrackingInputStreamFactory.create(
+ md5Calculation.streamToUse,
+ progressTrackerFactory.getProgressTracker()))
+ .build();
+ } else {
+ putObjectRequest =
+ PutObjectRequest.builder()
+ .copy(putObjectRequest)
+ .putObjectBody(
+ ProgressTrackingInputStreamFactory.create(
+ putObjectRequest.getPutObjectBody(),
+ progressTrackerFactory.getProgressTracker()))
.build();
}
@@ -104,6 +119,9 @@ private UploadResponse singleUpload(
private UploadResponse multipartUpload(UploadRequest uploadDetails) {
PutObjectRequest request = uploadDetails.putObjectRequest;
+ ProgressTrackerFactory progressTrackerFactory =
+ ProgressTrackerFactory.createMultiPartUploadProgressTrackerFactory(
+ uploadDetails.progressReporter, request.getContentLength());
long sizePerPart =
MultipartUtils.calculatePartSize(uploadConfiguration, request.getContentLength());
@@ -144,9 +162,17 @@ private UploadResponse multipartUpload(UploadRequest uploadDetails) {
if (uploadConfiguration.isEnforceMd5BeforeMultipartUpload()) {
MD5Calculation md5Calculation = calculateMd5(chunk, chunk.length());
assembler.addPart(
- md5Calculation.streamToUse, chunk.length(), md5Calculation.md5);
+ ProgressTrackingInputStreamFactory.create(
+ md5Calculation.streamToUse,
+ progressTrackerFactory.getProgressTracker()),
+ chunk.length(),
+ md5Calculation.md5);
} else {
- assembler.addPart(chunk, chunk.length(), null);
+ assembler.addPart(
+ ProgressTrackingInputStreamFactory.create(
+ chunk, progressTrackerFactory.getProgressTracker()),
+ chunk.length(),
+ null);
}
}
CommitMultipartUploadResponse response = assembler.commit();
@@ -287,6 +313,7 @@ public static class UploadRequest {
private final PutObjectRequest putObjectRequest;
private final ExecutorService parallelUploadExecutorService;
private final boolean allowOverwrite;
+ private final ProgressReporter progressReporter;
/**
* Creates a new {@link UploadRequestBuilder} using the given stream and content length. The stream and length will
@@ -328,6 +355,7 @@ public static class UploadRequestBuilder {
// always allow objects to be overwritten unless explicitly disabled
private boolean allowOverwrite = true;
private ExecutorService parallelUploadExecutorService;
+ private ProgressReporter progressReporter;
/**
* Configures whether or not the if-none-match header will be used to prevent
@@ -359,6 +387,18 @@ public UploadRequestBuilder parallelUploadExecutorService(
return this;
}
+ /**
+ * Sets the progress reporter that is used to notify of updates during the upload.
+ * If none is provided, then no progress updates shall be reported.
+ *
+ * @param progressReporter The progress reporter to use.
+ * @return This builder instance
+ */
+ public UploadRequestBuilder progressReporter(ProgressReporter progressReporter) {
+ this.progressReporter = progressReporter;
+ return this;
+ }
+
/**
* Builds a new UploadRequest instance. The body and content length will be set on the given
* request based on the original values provided when creating the builder.
@@ -376,7 +416,8 @@ public UploadRequest build(PutObjectRequest request) {
.ifNoneMatch(ifNoneMatch)
.build(),
parallelUploadExecutorService,
- allowOverwrite);
+ allowOverwrite,
+ progressReporter);
}
}
}
diff --git a/bmc-objectstorage/bmc-objectstorage-extensions/src/test/java/com/oracle/bmc/objectstorage/transfer/MultipartObjectAssemblerTest.java b/bmc-objectstorage/bmc-objectstorage-extensions/src/test/java/com/oracle/bmc/objectstorage/transfer/MultipartObjectAssemblerTest.java
index c39078227ef..abf3de8a8cf 100644
--- a/bmc-objectstorage/bmc-objectstorage-extensions/src/test/java/com/oracle/bmc/objectstorage/transfer/MultipartObjectAssemblerTest.java
+++ b/bmc-objectstorage/bmc-objectstorage-extensions/src/test/java/com/oracle/bmc/objectstorage/transfer/MultipartObjectAssemblerTest.java
@@ -10,7 +10,7 @@
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Matchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
diff --git a/bmc-objectstorage/bmc-objectstorage-extensions/src/test/java/com/oracle/bmc/objectstorage/transfer/ProgressTrackerTest.java b/bmc-objectstorage/bmc-objectstorage-extensions/src/test/java/com/oracle/bmc/objectstorage/transfer/ProgressTrackerTest.java
new file mode 100644
index 00000000000..28b5ca48c6a
--- /dev/null
+++ b/bmc-objectstorage/bmc-objectstorage-extensions/src/test/java/com/oracle/bmc/objectstorage/transfer/ProgressTrackerTest.java
@@ -0,0 +1,318 @@
+/**
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ */
+package com.oracle.bmc.objectstorage.transfer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.AdditionalMatchers.and;
+import static org.mockito.AdditionalMatchers.gt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.AdditionalMatchers.leq;
+
+import lombok.RequiredArgsConstructor;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class ProgressTrackerTest {
+ private static final long READ_CHUNK_SIZE = 8192L;
+ private static final long MAX_BLOCK_SIZE = 128 * 1024 * 1024;
+ private static final long EXECUTION_SERVICE_TIMEOUT_IN_SECONDS = 20;
+
+ @Test
+ public void nullProgressReporter() {
+ ProgressTrackerFactory progressTrackerFactory =
+ ProgressTrackerFactory.createSingleUploadProgressTrackerFactory(null, 0L);
+ assertNotNull(progressTrackerFactory);
+ assertNull(progressTrackerFactory.getProgressTracker());
+ progressTrackerFactory =
+ ProgressTrackerFactory.createMultiPartUploadProgressTrackerFactory(null, 0L);
+ assertNotNull(progressTrackerFactory);
+ assertNull(progressTrackerFactory.getProgressTracker());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void illegalProgressReport_overflow() {
+ ProgressTrackerFactory progressTrackerFactory =
+ ProgressTrackerFactory.createSingleUploadProgressTrackerFactory(
+ new ProgressReporter() {
+ @Override
+ public void onProgress(long completed, long total) {}
+ },
+ 0L);
+ progressTrackerFactory.getProgressTracker().onBytesRead(READ_CHUNK_SIZE);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void illegalProgressReport_underflow() {
+ ProgressTrackerFactory progressTrackerFactory =
+ ProgressTrackerFactory.createSingleUploadProgressTrackerFactory(
+ new ProgressReporter() {
+ @Override
+ public void onProgress(long completed, long total) {}
+ },
+ 0L);
+ progressTrackerFactory.getProgressTracker().onBytesRead(-1);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void illegalProgressReset() {
+ ProgressTrackerFactory progressTrackerFactory =
+ ProgressTrackerFactory.createSingleUploadProgressTrackerFactory(
+ new ProgressReporter() {
+ @Override
+ public void onProgress(long completed, long total) {}
+ },
+ 0L);
+ progressTrackerFactory.getProgressTracker().invalidateBytesRead(1);
+ }
+
+ @Test
+ public void singleUploadProgressTrackerFactory_sameProgressTracker() {
+ final ProgressTrackerFactory progressTrackerFactory =
+ ProgressTrackerFactory.createSingleUploadProgressTrackerFactory(
+ new ProgressReporter() {
+ @Override
+ public void onProgress(long completed, long total) {}
+ },
+ 0L);
+
+ assertNotNull(progressTrackerFactory);
+ assertSame(
+ progressTrackerFactory.getProgressTracker(),
+ progressTrackerFactory.getProgressTracker());
+ }
+
+ @Test
+ public void singleUploadProgressTrackerFactory_progressTrackerFunctionality()
+ throws InterruptedException {
+ final long totalBytes = ThreadLocalRandom.current().nextLong(MAX_BLOCK_SIZE);
+ final ProgressReporter mockProgressReporter = mock(ProgressReporter.class);
+ final ProgressTrackerFactory progressTrackerFactory =
+ ProgressTrackerFactory.createSingleUploadProgressTrackerFactory(
+ mockProgressReporter, totalBytes);
+
+ int count = trackProgressAndGetCallbackCount(progressTrackerFactory, totalBytes, 1);
+ verify(mockProgressReporter, times(count))
+ .onProgress(and(gt(0L), leq(totalBytes)), eq(totalBytes));
+ }
+
+ @Test
+ public void singleUploadProgressTrackerFactory_resetProgressTracker() {
+ final long totalBytes = ThreadLocalRandom.current().nextLong(MAX_BLOCK_SIZE);
+ final ProgressReporter mockProgressReporter = mock(ProgressReporter.class);
+ final ProgressTrackerFactory progressTrackerFactory =
+ ProgressTrackerFactory.createSingleUploadProgressTrackerFactory(
+ mockProgressReporter, totalBytes);
+ final ProgressTracker progressTracker = progressTrackerFactory.getProgressTracker();
+
+ progressTracker.reset();
+ progressTracker.onBytesRead(READ_CHUNK_SIZE);
+ progressTracker.onBytesRead(READ_CHUNK_SIZE);
+ progressTracker.reset();
+ progressTracker.onBytesRead(READ_CHUNK_SIZE);
+ progressTracker.onBytesRead(READ_CHUNK_SIZE);
+ progressTracker.onBytesRead(READ_CHUNK_SIZE);
+ progressTracker.onBytesRead(READ_CHUNK_SIZE);
+ progressTracker.reset();
+ progressTracker.reset();
+
+ ArgumentCaptor bytesReadArgumentCaptor = ArgumentCaptor.forClass(Long.class);
+ verify(mockProgressReporter, times(6))
+ .onProgress(bytesReadArgumentCaptor.capture(), eq(totalBytes));
+
+ List expectedBytesReadList =
+ Arrays.asList(
+ READ_CHUNK_SIZE,
+ 2 * READ_CHUNK_SIZE,
+ READ_CHUNK_SIZE,
+ 2 * READ_CHUNK_SIZE,
+ 3 * READ_CHUNK_SIZE,
+ 4 * READ_CHUNK_SIZE);
+ assertEquals(expectedBytesReadList, bytesReadArgumentCaptor.getAllValues());
+ }
+
+ @Test
+ public void multipartUploadProgressTrackerFactory_differentProgressTracker() {
+ final ProgressTrackerFactory progressTrackerFactory =
+ ProgressTrackerFactory.createMultiPartUploadProgressTrackerFactory(
+ new ProgressReporter() {
+ @Override
+ public void onProgress(long completed, long total) {}
+ },
+ 0L);
+
+ assertNotNull(progressTrackerFactory);
+ assertNotEquals(
+ progressTrackerFactory.getProgressTracker(),
+ progressTrackerFactory.getProgressTracker());
+ }
+
+ @Test
+ public void multipartUploadProgressTrackerFactory_progressTrackerFunctionality()
+ throws InterruptedException {
+ final long totalBytes = ThreadLocalRandom.current().nextLong(MAX_BLOCK_SIZE);
+ final ProgressReporter mockProgressReporter = mock(ProgressReporter.class);
+ final ProgressTrackerFactory progressTrackerFactory =
+ ProgressTrackerFactory.createMultiPartUploadProgressTrackerFactory(
+ mockProgressReporter, totalBytes);
+
+ final int invocationCount =
+ trackProgressAndGetCallbackCount(progressTrackerFactory, totalBytes, 30);
+
+ verify(mockProgressReporter, times(invocationCount))
+ .onProgress(and(gt(0L), leq(totalBytes)), eq(totalBytes));
+ }
+
+ @Test
+ public void multipartUploadProgressTrackerFactory_resetProgressTracker() {
+ final long totalBytes = ThreadLocalRandom.current().nextLong(MAX_BLOCK_SIZE);
+ final ProgressReporter mockProgressReporter = mock(ProgressReporter.class);
+ final ProgressTrackerFactory progressTrackerFactory =
+ ProgressTrackerFactory.createMultiPartUploadProgressTrackerFactory(
+ mockProgressReporter, totalBytes);
+ final ProgressTracker progressTracker1 = progressTrackerFactory.getProgressTracker();
+ final ProgressTracker progressTracker2 = progressTrackerFactory.getProgressTracker();
+
+ progressTracker1.onBytesRead(READ_CHUNK_SIZE);
+ progressTracker1.reset();
+ progressTracker2.reset();
+ progressTracker2.onBytesRead(READ_CHUNK_SIZE);
+ progressTracker1.onBytesRead(READ_CHUNK_SIZE);
+ progressTracker2.onBytesRead(READ_CHUNK_SIZE);
+ progressTracker1.reset();
+ progressTracker1.onBytesRead(READ_CHUNK_SIZE);
+ progressTracker2.reset();
+ progressTracker1.reset();
+ progressTracker1.reset();
+ progressTracker1.onBytesRead(READ_CHUNK_SIZE);
+
+ ArgumentCaptor bytesReadArgumentCaptor = ArgumentCaptor.forClass(Long.class);
+ verify(mockProgressReporter, times(6))
+ .onProgress(bytesReadArgumentCaptor.capture(), eq(totalBytes));
+
+ List expectedBytesReadList =
+ Arrays.asList(
+ READ_CHUNK_SIZE,
+ READ_CHUNK_SIZE,
+ 2 * READ_CHUNK_SIZE,
+ 3 * READ_CHUNK_SIZE,
+ 3 * READ_CHUNK_SIZE,
+ READ_CHUNK_SIZE);
+ assertEquals(expectedBytesReadList, bytesReadArgumentCaptor.getAllValues());
+ }
+
+ @Test
+ public void multipartUploadProgressTrackerFactory_simulateRealLifeScenario()
+ throws InterruptedException {
+ final long totalBytes = Long.MAX_VALUE;
+ final ProgressReporter mockProgressReporter = mock(ProgressReporter.class);
+ final ProgressTrackerFactory progressTrackerFactory =
+ ProgressTrackerFactory.createMultiPartUploadProgressTrackerFactory(
+ mockProgressReporter, totalBytes);
+
+ final List expectedBytesReadList =
+ Collections.synchronizedList(new ArrayList());
+ final AtomicLong totalBytesRead = new AtomicLong();
+ final int THREAD_POOL_SIZE = 3;
+ final int NUMBER_OF_CHUNKS = 10;
+ final int SIZE_OF_CHUNK = 128 * 1024 * 1024; // 128 MB
+ final ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
+
+ for (int i = 0; i < NUMBER_OF_CHUNKS; i++) {
+ executorService.submit(
+ new Runnable() {
+ final ProgressTracker progressTracker =
+ progressTrackerFactory.getProgressTracker();
+
+ @Override
+ public void run() {
+ long expectedBytesRead = 0;
+ for (int i = 0; i < SIZE_OF_CHUNK / READ_CHUNK_SIZE; i++) {
+ int diceRoll = ThreadLocalRandom.current().nextInt();
+ synchronized (expectedBytesReadList) {
+ if (diceRoll % 10 == 0) {
+ progressTracker.reset();
+ totalBytesRead.addAndGet(-1 * expectedBytesRead);
+ expectedBytesRead = 0;
+ } else {
+ progressTracker.onBytesRead(READ_CHUNK_SIZE);
+ expectedBytesRead += READ_CHUNK_SIZE;
+ expectedBytesReadList.add(
+ totalBytesRead.addAndGet(READ_CHUNK_SIZE));
+ }
+ }
+ }
+ }
+ });
+ }
+
+ executorService.shutdown();
+ executorService.awaitTermination(EXECUTION_SERVICE_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+
+ ArgumentCaptor bytesReadArgumentCaptor = ArgumentCaptor.forClass(Long.class);
+ verify(mockProgressReporter, times(expectedBytesReadList.size()))
+ .onProgress(bytesReadArgumentCaptor.capture(), eq(totalBytes));
+
+ assertEquals(expectedBytesReadList, bytesReadArgumentCaptor.getAllValues());
+ }
+
+ private static int trackProgressAndGetCallbackCount(
+ final ProgressTrackerFactory progressTrackerFactory,
+ final long totalBytes,
+ final int executorCount)
+ throws InterruptedException {
+ final AtomicInteger callbackCount = new AtomicInteger();
+ final ExecutorService executorService = Executors.newCachedThreadPool();
+
+ @RequiredArgsConstructor
+ abstract class MockProgressTrackerSource implements Runnable {
+ final ProgressTracker progressTracker;
+ final long totalBytesToRead;
+ }
+
+ for (int i = 0; i < executorCount; i++) {
+ executorService.submit(
+ new MockProgressTrackerSource(
+ progressTrackerFactory.getProgressTracker(),
+ (i == executorCount - 1)
+ ? totalBytes / executorCount + totalBytes % executorCount
+ : totalBytes / executorCount) {
+ @Override
+ public void run() {
+ long totalBytesRead = 0;
+ while (totalBytesRead < totalBytesToRead) {
+ final long bytesRead =
+ Math.min(
+ READ_CHUNK_SIZE, totalBytesToRead - totalBytesRead);
+ totalBytesRead += bytesRead;
+ callbackCount.getAndIncrement();
+ progressTracker.onBytesRead(bytesRead);
+ }
+ }
+ });
+ }
+
+ executorService.shutdown();
+ executorService.awaitTermination(EXECUTION_SERVICE_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+
+ return callbackCount.get();
+ }
+}
diff --git a/bmc-objectstorage/bmc-objectstorage-extensions/src/test/java/com/oracle/bmc/objectstorage/transfer/ProgressTrackingInputStreamTest.java b/bmc-objectstorage/bmc-objectstorage-extensions/src/test/java/com/oracle/bmc/objectstorage/transfer/ProgressTrackingInputStreamTest.java
new file mode 100644
index 00000000000..8085a741d19
--- /dev/null
+++ b/bmc-objectstorage/bmc-objectstorage-extensions/src/test/java/com/oracle/bmc/objectstorage/transfer/ProgressTrackingInputStreamTest.java
@@ -0,0 +1,100 @@
+/**
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ */
+package com.oracle.bmc.objectstorage.transfer;
+
+import com.oracle.bmc.io.DuplicatableInputStream;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProgressTrackingInputStreamTest {
+ abstract class DIStream extends InputStream implements DuplicatableInputStream {}
+
+ private static final InputStream inputStream = mock(InputStream.class);
+ private static final ProgressTracker progressTracker = mock(ProgressTracker.class);
+ private static final DIStream diStream = mock(DIStream.class);
+ private static final int READ_CHUNK_SIZE = 8192;
+
+ @Test
+ public void nullProgressTracker() {
+ final InputStream progressTrackingInputStream =
+ ProgressTrackingInputStreamFactory.create(inputStream, null);
+
+ assertSame(inputStream, progressTrackingInputStream);
+ }
+
+ @Test
+ public void nonDuplicatableStream() {
+ final InputStream progressTrackingInputStream =
+ ProgressTrackingInputStreamFactory.create(inputStream, progressTracker);
+
+ assertFalse(progressTrackingInputStream instanceof DuplicatableInputStream);
+ }
+
+ @Test
+ public void duplicatableStream() {
+ final InputStream progressTrackingInputStream =
+ ProgressTrackingInputStreamFactory.create(diStream, progressTracker);
+
+ assertTrue(progressTrackingInputStream instanceof DuplicatableInputStream);
+ }
+
+ @Test
+ public void duplicatableStream_duplicate() {
+ final InputStream progressTrackingInputStream =
+ ProgressTrackingInputStreamFactory.create(diStream, progressTracker);
+ doReturn(diStream).when(diStream).duplicate();
+
+ assertTrue(progressTrackingInputStream instanceof DuplicatableInputStream);
+ final DuplicatableInputStream duplicatableProgressTrackingInputStream =
+ (DuplicatableInputStream) progressTrackingInputStream;
+
+ final InputStream duplicateStream = duplicatableProgressTrackingInputStream.duplicate();
+ assertNotNull(duplicateStream);
+ assertTrue(duplicateStream instanceof DuplicatableInputStream);
+
+ verify(progressTracker, only()).reset();
+ }
+
+ @Test
+ public void duplicatableStream_readAndDuplicate() throws IOException {
+ final ProgressTracker progressTracker = mock(ProgressTracker.class);
+ final InputStream progressTrackingInputStream =
+ ProgressTrackingInputStreamFactory.create(diStream, progressTracker);
+ when(diStream.read(any(byte[].class))).thenReturn(READ_CHUNK_SIZE);
+ when(diStream.read()).thenReturn(1);
+ doReturn(diStream).when(diStream).duplicate();
+
+ final InOrder inOrder = Mockito.inOrder(progressTracker);
+ final byte[] buffer = new byte[READ_CHUNK_SIZE];
+ assertEquals(READ_CHUNK_SIZE, progressTrackingInputStream.read(buffer));
+ inOrder.verify(progressTracker).onBytesRead(eq((long) READ_CHUNK_SIZE));
+
+ doReturn(progressTracker).when(progressTracker).reset();
+ final InputStream duplicateStream =
+ ((DuplicatableInputStream) progressTrackingInputStream).duplicate();
+ inOrder.verify(progressTracker).reset();
+
+ assertEquals(1, duplicateStream.read());
+ inOrder.verify(progressTracker).onBytesRead(eq(1L));
+ assertEquals(READ_CHUNK_SIZE, duplicateStream.read(buffer));
+ inOrder.verify(progressTracker).onBytesRead(eq((long) READ_CHUNK_SIZE));
+ }
+}
diff --git a/bmc-objectstorage/bmc-objectstorage-extensions/src/test/java/com/oracle/bmc/objectstorage/transfer/UploadManagerTest.java b/bmc-objectstorage/bmc-objectstorage-extensions/src/test/java/com/oracle/bmc/objectstorage/transfer/UploadManagerTest.java
index 9a6258f92bf..9af9ccd861d 100644
--- a/bmc-objectstorage/bmc-objectstorage-extensions/src/test/java/com/oracle/bmc/objectstorage/transfer/UploadManagerTest.java
+++ b/bmc-objectstorage/bmc-objectstorage-extensions/src/test/java/com/oracle/bmc/objectstorage/transfer/UploadManagerTest.java
@@ -7,18 +7,35 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.AdditionalMatchers.and;
+import static org.mockito.AdditionalMatchers.gt;
+import static org.mockito.AdditionalMatchers.leq;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.ByteArrayInputStream;
+import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
-
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.oracle.bmc.objectstorage.model.MultipartUpload;
+import com.oracle.bmc.objectstorage.requests.CommitMultipartUploadRequest;
+import com.oracle.bmc.objectstorage.requests.CreateMultipartUploadRequest;
+import com.oracle.bmc.objectstorage.requests.UploadPartRequest;
+import com.oracle.bmc.objectstorage.responses.CreateMultipartUploadResponse;
+import com.oracle.bmc.objectstorage.responses.UploadPartResponse;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -35,10 +52,13 @@
import com.oracle.bmc.objectstorage.transfer.UploadManager.UploadResponse;
import com.oracle.bmc.objectstorage.transfer.internal.MultipartManifestImpl;
import com.oracle.bmc.util.StreamUtils;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
public class UploadManagerTest {
private static final String CONTENT = Strings.repeat("a", 2097152); // 2MiB
private static final long CONTENT_LENGTH = CONTENT.length();
+ private static final int READ_BLOCK_SIZE = 8192; // 8KB
private static final String CLIENT_REQ_ID = "clientReqId";
private static final String REQ_ID = "reqId";
private static final String CONTENT_TYPE = "application/text";
@@ -59,7 +79,7 @@ public void setUp() {
}
@Test
- public void upload_singleUpload() {
+ public void upload_singleUpload() throws IOException {
UploadConfiguration uploadConfiguration =
UploadConfiguration.builder().allowMultipartUploads(false).build();
UploadManager uploadManager = new UploadManager(objectStorage, uploadConfiguration);
@@ -84,7 +104,9 @@ public void upload_singleUpload() {
assertNull(uploadResponse.getMultipartMd5());
assertEquals(REQ_ID, uploadResponse.getOpcRequestId());
assertEquals(CLIENT_REQ_ID, uploadResponse.getOpcClientRequestId());
- assertSame(body, putRequestCaptor.getValue().getPutObjectBody());
+ byte[] buffer = new byte[(int) CONTENT_LENGTH];
+ putRequestCaptor.getValue().getPutObjectBody().read(buffer);
+ assertEquals(CONTENT, new String(buffer));
assertEquals(CONTENT_LENGTH, putRequestCaptor.getValue().getContentLength().longValue());
assertEquals(CLIENT_REQ_ID, putRequestCaptor.getValue().getOpcClientRequestId());
assertSame(METADATA, putRequestCaptor.getValue().getOpcMeta());
@@ -105,7 +127,6 @@ public void upload_singleUpload_enforceMd5() throws Exception {
UploadResponse uploadResponse = uploadManager.upload(request);
assertNotNull(uploadResponse);
- assertSame(body, putRequestCaptor.getValue().getPutObjectBody());
byte[] buffer = new byte[(int) CONTENT_LENGTH];
putRequestCaptor.getValue().getPutObjectBody().read(buffer);
assertEquals(CONTENT, new String(buffer));
@@ -313,7 +334,142 @@ protected MultipartObjectAssembler createAssembler(
}
}
- private UploadRequest createUploadRequest() {
+ @Test
+ public void singleUpload_progressReporter() throws IOException {
+ final UploadConfiguration uploadConfiguration =
+ UploadConfiguration.builder().allowMultipartUploads(false).build();
+ final UploadManager uploadManager = new UploadManager(objectStorage, uploadConfiguration);
+
+ final AtomicInteger expectedProgressNotificationCount = new AtomicInteger();
+ when(objectStorage.putObject(any(PutObjectRequest.class)))
+ .then(
+ new Answer() {
+ @Override
+ public PutObjectResponse answer(InvocationOnMock invocationOnMock)
+ throws Throwable {
+ final PutObjectRequest putObjectRequest =
+ invocationOnMock.getArgumentAt(0, null);
+ final InputStream inputStream = putObjectRequest.getPutObjectBody();
+ byte[] buffer = new byte[READ_BLOCK_SIZE];
+ while (inputStream.read(buffer) != -1) {
+ expectedProgressNotificationCount.getAndIncrement();
+ }
+ return PutObjectResponse.builder().build();
+ }
+ });
+
+ final ProgressReporter progressReporter = mock(ProgressReporter.class);
+ final UploadResponse uploadResponse =
+ uploadManager.upload(createUploadRequest(progressReporter));
+ assertNotNull(uploadResponse);
+ verify(progressReporter, times(expectedProgressNotificationCount.get()))
+ .onProgress(and(gt(0L), leq(CONTENT_LENGTH)), eq(CONTENT_LENGTH));
+ }
+
+ @Test
+ public void multipartUpload_progressReporter() {
+ final UploadManager uploadManager =
+ new UploadManager(objectStorage, getMultipartUploadConfiguration()) {
+ @Override
+ protected MultipartObjectAssembler createAssembler(
+ PutObjectRequest request,
+ UploadRequest uploadRequest,
+ ExecutorService executorService) {
+ return assembler;
+ }
+ };
+
+ final AtomicInteger onProgressCallbackCount = new AtomicInteger();
+ when(assembler.addPart(any(InputStream.class), anyLong(), anyString()))
+ .thenAnswer(
+ new Answer() {
+ @Override
+ public Integer answer(InvocationOnMock invocationOnMock)
+ throws Throwable {
+ final InputStream inputStream = invocationOnMock.getArgumentAt(0, null);
+ final byte[] buffer = new byte[READ_BLOCK_SIZE];
+ while (inputStream.read(buffer) != -1) {
+ onProgressCallbackCount.incrementAndGet();
+ }
+ return ThreadLocalRandom.current().nextInt();
+ }
+ });
+ when(assembler.commit()).thenReturn(CommitMultipartUploadResponse.builder().build());
+
+ final ProgressReporter progressReporter = mock(ProgressReporter.class);
+ final UploadResponse uploadResponse =
+ uploadManager.upload(createUploadRequest(progressReporter));
+ assertNotNull(uploadResponse);
+
+ verify(progressReporter, times(onProgressCallbackCount.get()))
+ .onProgress(and(gt(0L), leq(CONTENT_LENGTH)), eq(CONTENT_LENGTH));
+ }
+
+ @Test
+ public void multipartUpload_progressReporter_withRetries() {
+ final UploadManager uploadManager =
+ new UploadManager(objectStorage, getMultipartUploadConfiguration());
+
+ when(objectStorage.createMultipartUpload(any(CreateMultipartUploadRequest.class)))
+ .thenReturn(
+ CreateMultipartUploadResponse.builder()
+ .multipartUpload(MultipartUpload.builder().build())
+ .build());
+ final AtomicInteger onProgressCallbackCount = new AtomicInteger();
+ final ConcurrentMap retryCountMap = new ConcurrentHashMap<>();
+ when(objectStorage.uploadPart(any(UploadPartRequest.class)))
+ .thenAnswer(
+ new Answer() {
+ @Override
+ public UploadPartResponse answer(InvocationOnMock invocationOnMock)
+ throws Throwable {
+ final UploadPartRequest uploadPartRequest =
+ invocationOnMock.getArgumentAt(0, null);
+ final int uploadPartNum = uploadPartRequest.getUploadPartNum();
+ final InputStream inputStream =
+ uploadPartRequest.getUploadPartBody();
+ final byte[] buffer = new byte[READ_BLOCK_SIZE];
+ while (inputStream.read(buffer) != -1) {
+ onProgressCallbackCount.incrementAndGet();
+
+ if (!retryCountMap.containsKey(uploadPartNum)) {
+ retryCountMap.put(uploadPartNum, 0);
+ }
+ final int retryCount = retryCountMap.get(uploadPartNum);
+ final boolean shouldTriggerRetry =
+ ThreadLocalRandom.current().nextBoolean()
+ && retryCount < 2
+ && retryCountMap.replace(
+ uploadPartNum,
+ retryCount,
+ retryCount + 1);
+ if (shouldTriggerRetry) {
+ throw new BmcException(-1, null, null, null);
+ }
+ }
+ return UploadPartResponse.builder().build();
+ }
+ });
+ when(objectStorage.commitMultipartUpload(any(CommitMultipartUploadRequest.class)))
+ .thenReturn(CommitMultipartUploadResponse.builder().build());
+
+ final ProgressReporter progressReporter = mock(ProgressReporter.class);
+ final UploadResponse uploadResponse =
+ uploadManager.upload(createUploadRequest(progressReporter));
+ assertNotNull(uploadResponse);
+
+ verify(progressReporter, times(onProgressCallbackCount.get()))
+ .onProgress(and(gt(0L), leq(CONTENT_LENGTH)), eq(CONTENT_LENGTH));
+ }
+
+ private static UploadConfiguration getMultipartUploadConfiguration() {
+ return UploadConfiguration.builder()
+ .minimumLengthForMultipartUpload(0)
+ .minimumLengthPerUploadPart(1)
+ .build();
+ }
+
+ private UploadRequest createUploadRequest(ProgressReporter progressReporter) {
PutObjectRequest request =
PutObjectRequest.builder()
.opcMeta(METADATA)
@@ -322,6 +478,12 @@ private UploadRequest createUploadRequest() {
.contentType(CONTENT_TYPE)
.contentEncoding(CONTENT_ENCODING)
.build();
- return UploadRequest.builder(body, CONTENT_LENGTH).build(request);
+ return UploadRequest.builder(body, CONTENT_LENGTH)
+ .progressReporter(progressReporter)
+ .build(request);
+ }
+
+ private UploadRequest createUploadRequest() {
+ return createUploadRequest(null);
}
}
diff --git a/bmc-objectstorage/bmc-objectstorage-generated/pom.xml b/bmc-objectstorage/bmc-objectstorage-generated/pom.xml
index eb8ffe5cb68..2565e7f12e7 100644
--- a/bmc-objectstorage/bmc-objectstorage-generated/pom.xml
+++ b/bmc-objectstorage/bmc-objectstorage-generated/pom.xml
@@ -5,7 +5,7 @@
com.oracle.oci.sdk
oci-java-sdk
- 1.2.41
+ 1.2.42
../../pom.xml
@@ -18,7 +18,7 @@
com.oracle.oci.sdk
oci-java-sdk-common
- 1.2.41
+ 1.2.42
diff --git a/bmc-objectstorage/pom.xml b/bmc-objectstorage/pom.xml
index 3d4dec2efdb..eaf64ded3ca 100644
--- a/bmc-objectstorage/pom.xml
+++ b/bmc-objectstorage/pom.xml
@@ -5,7 +5,7 @@
com.oracle.oci.sdk
oci-java-sdk
- 1.2.41
+ 1.2.42
../pom.xml
@@ -19,17 +19,17 @@
com.oracle.oci.sdk
oci-java-sdk-common
- 1.2.41
+ 1.2.42
com.oracle.oci.sdk
oci-java-sdk-objectstorage-generated
- 1.2.41
+ 1.2.42
com.oracle.oci.sdk
oci-java-sdk-objectstorage-extensions
- 1.2.41
+ 1.2.42
diff --git a/bmc-shaded/bmc-shaded-full/pom.xml b/bmc-shaded/bmc-shaded-full/pom.xml
index 1fd6e72545c..d24b1c7ee3e 100644
--- a/bmc-shaded/bmc-shaded-full/pom.xml
+++ b/bmc-shaded/bmc-shaded-full/pom.xml
@@ -4,7 +4,7 @@
com.oracle.oci.sdk
oci-java-sdk-shaded
- 1.2.41
+ 1.2.42
../pom.xml
oci-java-sdk-shaded-full
diff --git a/bmc-shaded/pom.xml b/bmc-shaded/pom.xml
index f569ed0d729..0bfbd427ced 100644
--- a/bmc-shaded/pom.xml
+++ b/bmc-shaded/pom.xml
@@ -5,7 +5,7 @@
com.oracle.oci.sdk
oci-java-sdk
- 1.2.41
+ 1.2.42
../pom.xml
diff --git a/pom.xml b/pom.xml
index 2f4a9180e8a..3f75beb005b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0
com.oracle.oci.sdk
oci-java-sdk
- 1.2.41
+ 1.2.42
pom
Oracle Cloud Infrastructure SDK
This project contains the SDK used for Oracle Cloud Infrastructure
@@ -41,6 +41,8 @@
a breaking change. http://bouncy-castle.1462172.n4.nabble.com/Issues-when-migrating-to-1-53-td4657955.html
https://github.com/box/box-java-sdk/issues/168 Has not been fixed as of 1.55. -->
1.52
+ 1.10.19
+ 1.7.4
**/*IntegrationAutoTest.java
@@ -376,7 +378,19 @@
org.mockito
mockito-core
- 2.15.0
+ ${mockito.version}
+ test
+
+
+ org.powermock
+ powermock-module-junit4
+ ${powermock.version}
+ test
+
+
+ org.powermock
+ powermock-api-mockito
+ ${powermock.version}
test