1818
1919import static com .google .common .base .Preconditions .checkNotNull ;
2020
21+ import com .google .cloud .storage .BlobInfo ;
2122import com .google .cloud .storage .Storage .BlobWriteOption ;
2223import com .google .common .base .MoreObjects ;
2324import com .google .common .collect .ImmutableList ;
2425import java .util .List ;
2526import java .util .Objects ;
27+ import java .util .function .Function ;
2628import org .checkerframework .checker .nullness .qual .NonNull ;
2729
2830/**
3335public final class ParallelUploadConfig {
3436
3537 private final boolean skipIfExists ;
36- @ NonNull private final String prefix ;
3738 @ NonNull private final String bucketName ;
39+ @ NonNull private final UploadBlobInfoFactory uploadBlobInfoFactory ;
3840
3941 @ NonNull private final List <BlobWriteOption > writeOptsPerRequest ;
4042
4143 private ParallelUploadConfig (
4244 boolean skipIfExists ,
43- @ NonNull String prefix ,
4445 @ NonNull String bucketName ,
46+ @ NonNull UploadBlobInfoFactory uploadBlobInfoFactory ,
4547 @ NonNull List <BlobWriteOption > writeOptsPerRequest ) {
4648 this .skipIfExists = skipIfExists ;
47- this .prefix = prefix ;
4849 this .bucketName = bucketName ;
50+ this .uploadBlobInfoFactory = uploadBlobInfoFactory ;
4951 this .writeOptsPerRequest = applySkipIfExists (skipIfExists , writeOptsPerRequest );
5052 }
5153
@@ -63,9 +65,26 @@ public boolean isSkipIfExists() {
6365 * A common prefix that will be applied to all object paths in the destination bucket
6466 *
6567 * @see Builder#setPrefix(String)
68+ * @see Builder#setUploadBlobInfoFactory(UploadBlobInfoFactory)
69+ * @see UploadBlobInfoFactory#prefixObjectNames(String)
6670 */
6771 public @ NonNull String getPrefix () {
68- return prefix ;
72+ if (uploadBlobInfoFactory instanceof PrefixObjectNames ) {
73+ PrefixObjectNames prefixObjectNames = (PrefixObjectNames ) uploadBlobInfoFactory ;
74+ return prefixObjectNames .prefix ;
75+ }
76+ return "" ;
77+ }
78+
79+ /**
80+ * The {@link UploadBlobInfoFactory} which will be used to produce a {@link BlobInfo}s based on a
81+ * provided bucket name and file name.
82+ *
83+ * @see Builder#setUploadBlobInfoFactory(UploadBlobInfoFactory)
84+ * @since 2.49.0
85+ */
86+ public @ NonNull UploadBlobInfoFactory getUploadBlobInfoFactory () {
87+ return uploadBlobInfoFactory ;
6988 }
7089
7190 /**
@@ -96,22 +115,22 @@ public boolean equals(Object o) {
96115 }
97116 ParallelUploadConfig that = (ParallelUploadConfig ) o ;
98117 return skipIfExists == that .skipIfExists
99- && prefix .equals (that .prefix )
100118 && bucketName .equals (that .bucketName )
119+ && uploadBlobInfoFactory .equals (that .uploadBlobInfoFactory )
101120 && writeOptsPerRequest .equals (that .writeOptsPerRequest );
102121 }
103122
104123 @ Override
105124 public int hashCode () {
106- return Objects .hash (skipIfExists , prefix , bucketName , writeOptsPerRequest );
125+ return Objects .hash (skipIfExists , bucketName , uploadBlobInfoFactory , writeOptsPerRequest );
107126 }
108127
109128 @ Override
110129 public String toString () {
111130 return MoreObjects .toStringHelper (this )
112131 .add ("skipIfExists" , skipIfExists )
113- .add ("prefix" , prefix )
114132 .add ("bucketName" , bucketName )
133+ .add ("uploadBlobInfoFactory" , uploadBlobInfoFactory )
115134 .add ("writeOptsPerRequest" , writeOptsPerRequest )
116135 .toString ();
117136 }
@@ -137,13 +156,13 @@ private static List<BlobWriteOption> applySkipIfExists(
137156 public static final class Builder {
138157
139158 private boolean skipIfExists ;
140- private @ NonNull String prefix ;
141159 private @ NonNull String bucketName ;
160+ private @ NonNull UploadBlobInfoFactory uploadBlobInfoFactory ;
142161 private @ NonNull List <BlobWriteOption > writeOptsPerRequest ;
143162
144163 private Builder () {
145- this .prefix = "" ;
146164 this .bucketName = "" ;
165+ this .uploadBlobInfoFactory = UploadBlobInfoFactory .defaultInstance ();
147166 this .writeOptsPerRequest = ImmutableList .of ();
148167 }
149168
@@ -162,11 +181,37 @@ public Builder setSkipIfExists(boolean skipIfExists) {
162181 /**
163182 * Sets a common prefix that will be applied to all object paths in the destination bucket.
164183 *
184+ * <p><i>NOTE</i>: this method and {@link #setUploadBlobInfoFactory(UploadBlobInfoFactory)} are
185+ * mutually exclusive, and last invocation "wins".
186+ *
165187 * @return the builder instance with the value for prefix modified.
166188 * @see ParallelUploadConfig#getPrefix()
189+ * @see ParallelUploadConfig.Builder#setUploadBlobInfoFactory(UploadBlobInfoFactory)
190+ * @see UploadBlobInfoFactory#prefixObjectNames(String)
167191 */
168192 public Builder setPrefix (@ NonNull String prefix ) {
169- this .prefix = prefix ;
193+ this .uploadBlobInfoFactory = UploadBlobInfoFactory .prefixObjectNames (prefix );
194+ return this ;
195+ }
196+
197+ /**
198+ * Sets a {@link UploadBlobInfoFactory} which can be used to produce a custom BlobInfo based on
199+ * a provided bucket name and file name.
200+ *
201+ * <p>The bucket name in the returned BlobInfo MUST be equal to the value provided to {@link
202+ * #setBucketName(String)}, if not that upload will fail with a {@link
203+ * TransferStatus#FAILED_TO_START} and a {@link BucketNameMismatchException}.
204+ *
205+ * <p><i>NOTE</i>: this method and {@link #setPrefix(String)} are mutually exclusive, and last
206+ * invocation "wins".
207+ *
208+ * @return the builder instance with the value for uploadBlobInfoFactory modified.
209+ * @see ParallelUploadConfig#getPrefix()
210+ * @see ParallelUploadConfig#getUploadBlobInfoFactory()
211+ * @since 2.49.0
212+ */
213+ public Builder setUploadBlobInfoFactory (@ NonNull UploadBlobInfoFactory uploadBlobInfoFactory ) {
214+ this .uploadBlobInfoFactory = uploadBlobInfoFactory ;
170215 return this ;
171216 }
172217
@@ -199,10 +244,99 @@ public Builder setWriteOptsPerRequest(@NonNull List<BlobWriteOption> writeOptsPe
199244 * @return {@link ParallelUploadConfig}
200245 */
201246 public ParallelUploadConfig build () {
202- checkNotNull (prefix );
203247 checkNotNull (bucketName );
248+ checkNotNull (uploadBlobInfoFactory );
204249 checkNotNull (writeOptsPerRequest );
205- return new ParallelUploadConfig (skipIfExists , prefix , bucketName , writeOptsPerRequest );
250+ return new ParallelUploadConfig (
251+ skipIfExists , bucketName , uploadBlobInfoFactory , writeOptsPerRequest );
252+ }
253+ }
254+
255+ public interface UploadBlobInfoFactory {
256+
257+ /**
258+ * Method to produce a {@link BlobInfo} to be used for the upload to Cloud Storage.
259+ *
260+ * <p>The bucket name in the returned BlobInfo MUST be equal to the value provided to the {@link
261+ * ParallelUploadConfig.Builder#setBucketName(String)}, if not that upload will fail with a
262+ * {@link TransferStatus#FAILED_TO_START} and a {@link BucketNameMismatchException}.
263+ *
264+ * @param bucketName The name of the bucket to be uploaded to. The value provided here will be
265+ * the value from {@link ParallelUploadConfig#getBucketName()}.
266+ * @param fileName The String representation of the absolute path of the file to be uploaded
267+ * @return The instance of {@link BlobInfo} that should be used to upload the file to Cloud
268+ * Storage.
269+ */
270+ BlobInfo apply (String bucketName , String fileName );
271+
272+ /**
273+ * Adapter factory to provide the same semantics as if using {@link Builder#setPrefix(String)}
274+ */
275+ static UploadBlobInfoFactory prefixObjectNames (String prefix ) {
276+ return new PrefixObjectNames (prefix );
277+ }
278+
279+ /** The default instance which applies not modification to the provided {@code fileName} */
280+ static UploadBlobInfoFactory defaultInstance () {
281+ return DefaultUploadBlobInfoFactory .INSTANCE ;
282+ }
283+
284+ /**
285+ * Convenience method to "lift" a {@link Function} that transforms the file name to an {@link
286+ * UploadBlobInfoFactory}
287+ */
288+ static UploadBlobInfoFactory transformFileName (Function <String , String > fileNameTransformer ) {
289+ return (b , f ) -> BlobInfo .newBuilder (b , fileNameTransformer .apply (f )).build ();
290+ }
291+ }
292+
293+ private static final class DefaultUploadBlobInfoFactory implements UploadBlobInfoFactory {
294+ private static final DefaultUploadBlobInfoFactory INSTANCE = new DefaultUploadBlobInfoFactory ();
295+
296+ private DefaultUploadBlobInfoFactory () {}
297+
298+ @ Override
299+ public BlobInfo apply (String bucketName , String fileName ) {
300+ return BlobInfo .newBuilder (bucketName , fileName ).build ();
301+ }
302+ }
303+
304+ private static final class PrefixObjectNames implements UploadBlobInfoFactory {
305+ private final String prefix ;
306+
307+ private PrefixObjectNames (String prefix ) {
308+ this .prefix = prefix ;
309+ }
310+
311+ @ Override
312+ public BlobInfo apply (String bucketName , String fileName ) {
313+ String separator = "" ;
314+ if (!fileName .startsWith ("/" )) {
315+ separator = "/" ;
316+ }
317+ return BlobInfo .newBuilder (bucketName , prefix + separator + fileName ).build ();
318+ }
319+
320+ @ Override
321+ public boolean equals (Object o ) {
322+ if (this == o ) {
323+ return true ;
324+ }
325+ if (!(o instanceof PrefixObjectNames )) {
326+ return false ;
327+ }
328+ PrefixObjectNames that = (PrefixObjectNames ) o ;
329+ return Objects .equals (prefix , that .prefix );
330+ }
331+
332+ @ Override
333+ public int hashCode () {
334+ return Objects .hashCode (prefix );
335+ }
336+
337+ @ Override
338+ public String toString () {
339+ return MoreObjects .toStringHelper (this ).add ("prefix" , prefix ).toString ();
206340 }
207341 }
208342}
0 commit comments