3030import com .netflix .conductor .client .http .ConductorClient ;
3131import com .netflix .conductor .client .http .ConductorClientRequest ;
3232import com .netflix .conductor .client .http .ConductorClientRequest .Method ;
33+ import com .netflix .conductor .sdk .workflow .executor .task .TaskContext ;
3334
3435import com .fasterxml .jackson .core .type .TypeReference ;
3536
5152 */
5253public class FileClient implements FileUploader {
5354
54- private static final TypeReference <FileUploadResponse > FILE_UPLOAD_RESPONSE_TYPE = new TypeReference <>() {};
55- private static final TypeReference <FileUploadUrlResponse > FILE_UPLOAD_URL_RESPONSE_TYPE = new TypeReference <>() {};
56- private static final TypeReference <FileUploadCompleteResponse > FILE_UPLOAD_COMPLETE_RESPONSE_TYPE = new TypeReference <>() {};
57- private static final TypeReference <FileDownloadUrlResponse > FILE_DOWNLOAD_URL_RESPONSE_TYPE = new TypeReference <>() {};
58- private static final TypeReference <FileHandle > FILE_HANDLE_TYPE = new TypeReference <>() {};
59- private static final TypeReference <MultipartInitResponse > MULTIPART_INIT_RESPONSE_TYPE = new TypeReference <>() {};
55+ private static final TypeReference <FileUploadResponse > FILE_UPLOAD_RESPONSE_TYPE = new TypeReference <>() {
56+ };
57+ private static final TypeReference <FileUploadUrlResponse > FILE_UPLOAD_URL_RESPONSE_TYPE = new TypeReference <>() {
58+ };
59+ private static final TypeReference <FileUploadCompleteResponse > FILE_UPLOAD_COMPLETE_RESPONSE_TYPE = new TypeReference <>() {
60+ };
61+ private static final TypeReference <FileDownloadUrlResponse > FILE_DOWNLOAD_URL_RESPONSE_TYPE = new TypeReference <>() {
62+ };
63+ private static final TypeReference <FileHandle > FILE_HANDLE_TYPE = new TypeReference <>() {
64+ };
65+ private static final TypeReference <MultipartInitResponse > MULTIPART_INIT_RESPONSE_TYPE = new TypeReference <>() {
66+ };
6067
6168 private final ConductorClient client ;
6269 private final FileClientProperties properties ;
@@ -73,8 +80,8 @@ public FileClient(ConductorClient client) {
7380 /**
7481 * Full-args constructor.
7582 *
76- * @param properties {@code null} to use defaults
77- * @param fileStorageBackendsByStorageType {@code null} to use the built-in backends
83+ * @param properties {@code null} to use defaults
84+ * @param fileStorageBackendsByStorageType {@code null} to use the built-in backends
7885 */
7986 public FileClient (ConductorClient client , FileClientProperties properties , Map <StorageType , FileStorageBackend > fileStorageBackendsByStorageType ) {
8087 this .client = client ;
@@ -84,25 +91,24 @@ public FileClient(ConductorClient client, FileClientProperties properties, Map<S
8491
8592 // --- FileUploader (developer-facing) ---
8693
87- @ Override
88- public FileHandler upload (Path localFile ) {
89- return upload (localFile , "application/octet-stream" );
90- }
91-
9294 /**
93- * Uploads a local file. Creates the server-side metadata record, selects the backend that
94- * matches the server-reported {@link StorageType}, and uses multipart when the file size
95- * exceeds {@link FileClientProperties#getMultipartThreshold()} AND the backend supports
96- * multipart (see {@link FileStorageBackend#hasMultipartSupport()}).
95+ * Uploads a local file with the given {@link FileUploadOptions}.
96+ *
97+ * <p>{@code workflowId} is required; pass {@code null} only if you are prepared to see a
98+ * {@link FileStorageException}. When called from inside a worker and {@code options.taskId}
99+ * is null, {@code taskId} is auto-filled from the active {@link TaskContext}.
97100 *
98- * @throws FileStorageException if no backend is registered for the server's storage type,
99- * or if any step of the upload fails
101+ * @throws FileStorageException if {@code workflowId} is null, if no backend is registered for
102+ * the server's storage type, or if any step of the upload fails
100103 */
101104 @ Override
102- public FileHandler upload (Path localFile , String contentType ) {
105+ public FileHandler upload (String workflowId , Path localFile , FileUploadOptions options ) {
106+ if (workflowId == null ) {
107+ throw new FileStorageException ("workflowId is required" );
108+ }
103109 try {
104- long fileSize = Files . size ( localFile );
105- FileUploadRequest request = FileHandlerConverter .toFileUploadRequest (localFile , contentType , fileSize );
110+ fillDefaults ( options , localFile );
111+ FileUploadRequest request = FileHandlerConverter .toFileUploadRequest (workflowId , options );
106112
107113 FileUploadResponse response = createFileOnServer (request );
108114
@@ -112,7 +118,7 @@ public FileHandler upload(Path localFile, String contentType) {
112118 + " but SDK only supports: " + fileStorageBackendsByStorageType .keySet ());
113119 }
114120
115- if (fileSize > properties .getMultipartThreshold () && storageBackend .hasMultipartSupport ()) {
121+ if (options . getFileSize () > properties .getMultipartThreshold () && storageBackend .hasMultipartSupport ()) {
116122 uploadMultipart (response .getFileHandleId (), response .getStorageType (), localFile );
117123 } else {
118124 storageBackend .upload (response .getUploadUrl (), localFile );
@@ -125,22 +131,52 @@ public FileHandler upload(Path localFile, String contentType) {
125131 }
126132 }
127133
134+ /**
135+ * Buffers the stream to a temp file, then delegates to {@link #upload(String, Path, FileUploadOptions)}.
136+ */
128137 @ Override
129- public FileHandler upload (InputStream inputStream ) {
130- return upload (inputStream , "application/octet-stream" );
131- }
132-
133- @ Override
134- public FileHandler upload (InputStream inputStream , String contentType ) {
138+ public FileHandler upload (String workflowId , InputStream inputStream , FileUploadOptions options ) {
135139 try {
136140 Path temp = Files .createTempFile ("conductor-upload-" , ".tmp" );
137- Files .copy (inputStream , temp , java . nio . file . StandardCopyOption .REPLACE_EXISTING );
138- return upload (temp , contentType );
141+ Files .copy (inputStream , temp , StandardCopyOption .REPLACE_EXISTING );
142+ return upload (workflowId , temp , options );
139143 } catch (IOException e ) {
140144 throw new FileStorageException ("Failed to write InputStream to temp file" , e );
141145 }
142146 }
143147
148+ @ Override
149+ public FileHandler upload (String workflowId , Path localFile ) {
150+ return upload (workflowId , localFile , new FileUploadOptions ());
151+ }
152+
153+ @ Override
154+ public FileHandler upload (String workflowId , InputStream inputStream ) {
155+ return upload (workflowId , inputStream , new FileUploadOptions ());
156+ }
157+
158+ /**
159+ * Populates unset options from the local file and active {@link TaskContext}. Mutates
160+ * {@code options} in place — SDK-internal helper only.
161+ */
162+ private static void fillDefaults (FileUploadOptions options , Path localFile ) throws IOException {
163+ if (options .getFileName () == null ) {
164+ options .setFileName (localFile .getFileName ().toString ());
165+ }
166+ if (options .getFileSize () == 0 ) {
167+ options .setFileSize (Files .size (localFile ));
168+ }
169+ if (options .getContentType () == null ) {
170+ options .setContentType ("application/octet-stream" );
171+ }
172+ if (options .getTaskId () == null ) {
173+ TaskContext ctx = TaskContext .get ();
174+ if (ctx != null ) {
175+ options .setTaskId (ctx .getTaskId ());
176+ }
177+ }
178+ }
179+
144180 // --- SDK-internal (public for cross-package access, not developer-facing) ---
145181
146182 /**
@@ -158,7 +194,9 @@ public void download(String workflowId, String fileHandleId, StorageType storage
158194 fileStorageBackendsByStorageType .get (storageType ).download (urlResponse .getDownloadUrl (), destination );
159195 }
160196
161- /** Fetches the {@link FileHandle} metadata via {@code GET /api/files/{fileId}}. */
197+ /**
198+ * Fetches the {@link FileHandle} metadata via {@code GET /api/files/{fileId}}.
199+ */
162200 public FileHandle getMetadata (String fileHandleId ) {
163201 ConductorClientRequest request = ConductorClientRequest .builder ()
164202 .method (Method .GET )
0 commit comments