66import com .azure .storage .blob .BlobContainerClient ;
77import com .azure .storage .blob .BlobServiceClient ;
88import com .azure .storage .blob .BlobServiceClientBuilder ;
9+ import com .azure .storage .blob .models .BlobDownloadContentResponse ;
910import com .azure .storage .blob .models .BlobHttpHeaders ;
1011import com .azure .storage .blob .models .BlobStorageException ;
12+ import com .azure .storage .blob .options .BlobParallelUploadOptions ;
1113import com .microsoft .durabletask .PayloadStore ;
1214
1315import java .io .ByteArrayInputStream ;
@@ -43,6 +45,7 @@ public final class BlobPayloadStore implements PayloadStore {
4345 private final String blobPrefix ;
4446 private final String containerName ;
4547 private final boolean compressPayloads ;
48+ private volatile boolean containerEnsured ;
4649
4750 /**
4851 * Creates a new BlobPayloadStore with the given options.
@@ -72,8 +75,6 @@ public BlobPayloadStore(BlobPayloadStoreOptions options) {
7275 this .blobPrefix = options .getBlobPrefix ();
7376 this .containerName = options .getContainerName ();
7477 this .compressPayloads = options .isCompressPayloads ();
75-
76- ensureContainerExists ();
7778 }
7879
7980 @ Override
@@ -82,6 +83,8 @@ public String upload(String payload) {
8283 throw new IllegalArgumentException ("payload must not be null" );
8384 }
8485
86+ ensureContainerExists ();
87+
8588 String blobName = this .blobPrefix + UUID .randomUUID ().toString ().replace ("-" , "" ) + BLOB_EXTENSION ;
8689 BlobClient blobClient = this .containerClient .getBlobClient (blobName );
8790
@@ -90,8 +93,11 @@ public String upload(String payload) {
9093
9194 if (this .compressPayloads ) {
9295 data = gzipCompress (rawData );
93- blobClient .upload (new ByteArrayInputStream (data ), data .length , true );
94- blobClient .setHttpHeaders (new BlobHttpHeaders ().setContentEncoding (GZIP_CONTENT_ENCODING ));
96+ BlobHttpHeaders headers = new BlobHttpHeaders ().setContentEncoding (GZIP_CONTENT_ENCODING );
97+ BlobParallelUploadOptions uploadOptions = new BlobParallelUploadOptions (
98+ new ByteArrayInputStream (data ), data .length )
99+ .setHeaders (headers );
100+ blobClient .uploadWithResponse (uploadOptions , null , null );
95101 } else {
96102 data = rawData ;
97103 blobClient .upload (new ByteArrayInputStream (data ), data .length , true );
@@ -112,13 +118,9 @@ public String download(String token) {
112118 String blobName = extractBlobName (token );
113119 BlobClient blobClient = this .containerClient .getBlobClient (blobName );
114120
115- ByteArrayOutputStream outputStream = new ByteArrayOutputStream ();
116- blobClient .downloadStream (outputStream );
117-
118- byte [] rawBytes = outputStream .toByteArray ();
119-
120- // Check if the blob was compressed by inspecting content encoding
121- String contentEncoding = blobClient .getProperties ().getContentEncoding ();
121+ BlobDownloadContentResponse response = blobClient .downloadContentWithResponse (null , null , null , null );
122+ byte [] rawBytes = response .getValue ().toBytes ();
123+ String contentEncoding = response .getDeserializedHeaders ().getContentEncoding ();
122124 if (GZIP_CONTENT_ENCODING .equalsIgnoreCase (contentEncoding )) {
123125 rawBytes = gzipDecompress (rawBytes );
124126 }
@@ -137,21 +139,27 @@ public boolean isKnownPayloadToken(String value) {
137139 }
138140
139141 private void ensureContainerExists () {
142+ if (this .containerEnsured ) {
143+ return ;
144+ }
140145 try {
141146 if (!this .containerClient .exists ()) {
142147 this .containerClient .create ();
143148 logger .info (() -> String .format ("Created blob container: %s" , this .containerClient .getBlobContainerName ()));
144149 }
150+ this .containerEnsured = true ;
145151 } catch (BlobStorageException e ) {
146152 // Container might have been created concurrently (409 Conflict)
147153 if (e .getStatusCode () != 409 ) {
148154 throw e ;
149155 }
156+ this .containerEnsured = true ;
150157 }
151158 }
152159
153160 /**
154- * Extracts the blob name from a {@code blob:v1:<container>:<blobName>} token.
161+ * Extracts the blob name from a {@code blob:v1:<container>:<blobName>} token
162+ * and validates that the container matches the configured container.
155163 */
156164 private String extractBlobName (String token ) {
157165 if (!token .startsWith (TOKEN_PREFIX )) {
@@ -165,6 +173,12 @@ private String extractBlobName(String token) {
165173 throw new IllegalArgumentException (
166174 "Token does not have the expected format (blob:v1:<container>:<blobName>): " + token );
167175 }
176+ String tokenContainer = remainder .substring (0 , colonIndex );
177+ if (!this .containerName .equals (tokenContainer )) {
178+ throw new IllegalArgumentException (String .format (
179+ "Token container '%s' does not match configured container '%s'" ,
180+ tokenContainer , this .containerName ));
181+ }
168182 return remainder .substring (colonIndex + 1 );
169183 }
170184
0 commit comments