• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Bynder / bynder-java-sdk / 22679302217

04 Mar 2026 04:42PM UTC coverage: 39.99% (+0.03%) from 39.959%
22679302217

push

github

web-flow
API-2609: Include the ability to mark asset as public during Upload (#150)

* API-2609 add isPublic to upload query as a param

* API-2609 update unit test

3 of 6 new or added lines in 3 files covered. (50.0%)

1 existing line in 1 file now uncovered.

791 of 1978 relevant lines covered (39.99%)

0.4 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

5.62
/src/main/java/com/bynder/sdk/service/upload/FileUploader.java
1
/*
2
 * Copyright (c) 2017 Bynder B.V. All rights reserved.
3
 * <p>
4
 * Licensed under the MIT License. See LICENSE file in the project root for full license
5
 * information.
6
 */
7
package com.bynder.sdk.service.upload;
8

9
import com.bynder.sdk.api.BynderApi;
10
import com.bynder.sdk.exception.BynderUploadException;
11
import com.bynder.sdk.model.upload.*;
12
import com.bynder.sdk.query.decoder.QueryDecoder;
13
import com.bynder.sdk.query.upload.*;
14
import com.bynder.sdk.service.amazons3.AmazonS3Service;
15
import com.bynder.sdk.util.RXUtils;
16
import io.reactivex.Completable;
17
import io.reactivex.Observable;
18
import io.reactivex.Single;
19
import org.slf4j.Logger;
20
import org.slf4j.LoggerFactory;
21

22
import java.io.IOException;
23
import java.nio.file.Files;
24
import java.nio.file.Paths;
25
import java.util.Map;
26
import java.util.concurrent.TimeUnit;
27

28
/**
29
 * Class used to upload files to Bynder.
30
 */
31
public class FileUploader {
32

33
    private static final Logger LOG = LoggerFactory.getLogger(FileUploader.class);
1✔
34

35

36
    private static final int MAX_CHUNK_SIZE = 1024 * 1024 * 5;
37

38
    /**
39
     * Max polling iterations to wait for the file to be converted.
40
     */
41
    private static final int MAX_POLLING_ITERATIONS = 240;
42

43
    /**
44
     * Idle time between polling iterations.
45
     */
46
    private static final int POLLING_IDLE_TIME = 2000;
47

48
    /**
49
     * Instance of {@link BynderApi} which handles the HTTP communication.
50
     */
51
    private final BynderApi bynderApi;
52

53
    /**
54
     * Instance of {@link QueryDecoder} to decode query objects into API parameters.
55
     */
56
    private final QueryDecoder queryDecoder;
57

58
    /**
59
     * Creates a new instance of the class.
60
     *
61
     * @param bynderApi    Instance to handle the HTTP communication with the Bynder API.
62
     * @param queryDecoder Query decoder.
63
     */
64
    public FileUploader(final BynderApi bynderApi, final QueryDecoder queryDecoder) {
1✔
65
        this.bynderApi = bynderApi;
1✔
66
        this.queryDecoder = queryDecoder;
1✔
67
    }
1✔
68

69
    /**
70
     * Uploads a file with the information specified in the query parameter.
71
     *
72
     * @param uploadQuery Upload query with the information to upload the file.
73
     * @return {@link Observable} with the {@link SaveMediaResponse} information.
74
     */
75
    public Single<SaveMediaResponse> uploadFile(final UploadQuery uploadQuery) {
76
        return getClosestS3Endpoint().flatMap(awsBucket -> {
×
77
            String filename = uploadQuery.getFilename();
×
78
            AmazonS3Service amazonS3Service = AmazonS3Service.Builder.create(awsBucket);
×
79
            return getUploadInformation(new RequestUploadQuery(filename))
×
80
                    .flatMap(uploadRequest -> uploadChunk(
×
81
                            amazonS3Service,
82
                            uploadRequest,
83
                            uploadQuery,
84
                            filename
85
                    ).count().flatMap(chunkCount -> finaliseUpload(new FinaliseUploadQuery(
×
86
                            uploadRequest.getS3File().getUploadId(),
×
87
                            uploadRequest.getS3File().getTargetId(),
×
88
                            uploadRequest.getS3Filename(),
×
89
                            chunkCount
90
                    ))));
91
        }).flatMap(importId ->
×
92
                pollProcessing(importId).andThen(saveUploadedMedia(importId, uploadQuery))
×
93
        );
94
    }
95

96
    public Single<UploadAdditionalMediaResponse> uploadAdditionalFile(final UploadQuery uploadQuery) {
97
        return getClosestS3Endpoint().flatMap(awsBucket -> {
×
98
            String filename = uploadQuery.getFilename();
×
99
            AmazonS3Service amazonS3Service = AmazonS3Service.Builder.create(awsBucket);
×
100
            return getUploadInformation(new RequestUploadQuery(filename))
×
101
                    .flatMap(uploadRequest -> uploadChunk(
×
102
                            amazonS3Service,
103
                            uploadRequest,
104
                            uploadQuery,
105
                            filename
106
                    ).count().flatMap(chunkCount -> finaliseUploadAdditional(new FinaliseUploadAdditionalQuery(
×
107
                            uploadRequest.getS3File().getUploadId(),
×
108
                            uploadRequest.getS3File().getTargetId(),
×
109
                            uploadRequest.getS3Filename(),
×
110
                            chunkCount
111
                    ), uploadQuery.getMediaId())));
×
112
        });
113
    }
114

115
    /**
116
     * Uploads a file with the information specified in the query parameter
117
     * while providing information on the progress of the upload via the Observable returned.
118
     *
119
     * @param uploadQuery Upload query with the information to upload the file.
120
     * @return {@link Observable} with the {@link UploadProgress} information.
121
     */
122
    public Observable<UploadProgress> uploadFileWithProgress(final UploadQuery uploadQuery) {
123
        String filename = uploadQuery.getFilename();
×
124
        return getClosestS3Endpoint().flatMapObservable(awsBucket -> {
×
125
            AmazonS3Service amazonS3Service = AmazonS3Service.Builder.create(awsBucket);
×
126
            return getUploadInformation(new RequestUploadQuery(filename))
×
127
                    .flatMapObservable(uploadRequest -> uploadChunk(
×
128
                            amazonS3Service,
129
                            uploadRequest,
130
                            uploadQuery,
131
                            filename
132
                    ));
133
        });
134
    }
135

136
    private Observable<UploadProgress> uploadChunk(
137
            AmazonS3Service amazonS3Service,
138
            UploadRequest uploadRequest,
139
            UploadQuery uploadQuery,
140
            String filename
141
    ) throws IOException {
142
        long fileSize = Files.size(Paths.get(uploadQuery.getFilepath()));
×
143
        UploadProgress uploadProgress = new UploadProgress(fileSize);
×
144
        return RXUtils.mapWithIndex(
×
145
                RXUtils.readFileChunks(uploadQuery.getFilepath(), MAX_CHUNK_SIZE),
×
146
                1
147
        ).flatMapSingle(chunk -> amazonS3Service.uploadPartToAmazon(
×
148
                chunk,
149
                filename,
150
                (int) ((fileSize - 1) / MAX_CHUNK_SIZE + 1),
151
                uploadRequest.getMultipartParams()
×
152
        ).andThen(registerChunk(new RegisterChunkQuery(
×
153
                chunk.getIndex(),
×
154
                uploadRequest.getS3File().getUploadId(),
×
155
                uploadRequest.getS3File().getTargetId(),
×
156
                String.format(
×
157
                        "%s/p%s",
158
                        uploadRequest.getS3Filename(),
×
159
                        chunk.getIndex()
×
160
                )
161
        ))).toSingle(() -> uploadProgress.addProgress(chunk.getValue().length)));
×
162
    }
163

164
    /**
165
     * Check {@link BynderApi#getPollStatus} for more information.
166
     */
167
    private Completable pollProcessing(final String importId) {
168
        LOG.info("Polling status for importId: " + importId);
×
169
        return getPollStatus(new PollStatusQuery(importId.split(",")))
×
170
                .delay(POLLING_IDLE_TIME, TimeUnit.MILLISECONDS)
×
171
                .repeat()
×
172
                .take(MAX_POLLING_ITERATIONS)
×
173
                .takeUntil(pollStatus -> pollStatus.processingDone(importId) || pollStatus.processingFailed(importId))
×
174
                .lastElement()
×
175
                .toSingle()
×
176
                .flatMapCompletable(pollStatus -> {
×
177
                    if (pollStatus.processingFailed(importId)) {
×
178
                        return Completable.error(new BynderUploadException("Processing media failed."));
×
179
                    }
180
                    return Completable.complete();
×
181
                });
182
    }
183

184
    /**
185
     * Calls {@link FileUploader#saveMedia(SaveMediaQuery)} to save the completely uploaded file in
186
     * Bynder.
187
     *
188
     * @param uploadQuery Upload query with the information to upload the file.
189
     * @return {@link Single} with the {@link SaveMediaResponse} information.
190
     */
191
    private Single<SaveMediaResponse> saveUploadedMedia(final String importId, final UploadQuery uploadQuery) {
192
        SaveMediaQuery saveMediaQuery = new SaveMediaQuery(importId)
×
193
                .setAudit(uploadQuery.isAudit())
×
NEW
194
                .setIsPublic(uploadQuery.getIsPublic())
×
UNCOV
195
                .setMetaproperties(uploadQuery.getMetaproperties());
×
196

197
        if (uploadQuery.getMediaId() == null) {
×
198
            // A new asset will be created for the uploaded file.
199
            return saveMedia(saveMediaQuery
×
200
                    .setBrandId(uploadQuery.getBrandId())
×
201
                    .setName(uploadQuery.getAssetName())
×
202
                    .setTags(uploadQuery.getTags())
×
203
            );
204
        } else {
205
            // The uploaded file will be attached to an existing asset.
206
            return saveMedia(saveMediaQuery
×
207
                    .setMediaId(uploadQuery.getMediaId())
×
208
            );
209
        }
210
    }
211

212
    /**
213
     * Check {@link BynderApi#getClosestS3Endpoint()} for more information.
214
     */
215
    private Single<String> getClosestS3Endpoint() {
216
        return bynderApi.getClosestS3Endpoint().singleOrError().map(RXUtils::getResponseBody);
×
217
    }
218

219
    /**
220
     * Check {@link BynderApi#getUploadInformation(Map)} for more information.
221
     */
222
    private Single<UploadRequest> getUploadInformation(final RequestUploadQuery requestUploadQuery) {
223
        Map<String, String> params = queryDecoder.decode(requestUploadQuery);
×
224
        return bynderApi.getUploadInformation(params).singleOrError().map(RXUtils::getResponseBody);
×
225
    }
226

227
    /**
228
     * Check {@link BynderApi#registerChunk(Map)} for more information.
229
     */
230
    private Completable registerChunk(final RegisterChunkQuery registerChunkQuery) {
231
        Map<String, String> params = queryDecoder.decode(registerChunkQuery);
×
232
        return bynderApi.registerChunk(params).ignoreElements();
×
233
    }
234

235
    /**
236
     * Check {@link BynderApi#finaliseUpload(Map)} for more information.
237
     */
238
    private Single<String> finaliseUpload(final FinaliseUploadQuery finaliseUploadQuery) {
239
        Map<String, String> params = queryDecoder.decode(finaliseUploadQuery);
×
240
        return bynderApi.finaliseUpload(params).singleOrError().map(RXUtils::getResponseBody)
×
241
                .map(FinaliseResponse::getImportId);
×
242
    }
243

244
    private Single<UploadAdditionalMediaResponse> finaliseUploadAdditional(final FinaliseUploadAdditionalQuery finaliseUploadQuery, String mediaId) {
245
        Map<String, String> params = queryDecoder.decode(finaliseUploadQuery);
×
246
        return bynderApi.finaliseUploadAdditional(mediaId, params).singleOrError().map(RXUtils::getResponseBody);
×
247
    }
248

249
    /**
250
     * Check {@link BynderApi#getPollStatus(Map)} for more information.
251
     */
252
    private Single<PollStatus> getPollStatus(final PollStatusQuery pollStatusQuery) {
253
        Map<String, String> params = queryDecoder.decode(pollStatusQuery);
×
254
        LOG.info("Polling status for: " + params);
×
255
        return bynderApi.getPollStatus(params)
×
256
                .doOnNext(response -> {
×
257
                    if (response.raw() != null && response.raw().request() != null) {
×
258
                        LOG.info("Request URL for poll: " + response.raw().request().url());
×
259
                    }
260
                })
×
261
                .singleOrError()
×
262
                .map(RXUtils::getResponseBody);
×
263
    }
264

265
    /**
266
     * Check {@link BynderApi#saveMedia(Map)} for more information.
267
     */
268
    private Single<SaveMediaResponse> saveMedia(final SaveMediaQuery saveMediaQuery) {
269
        Map<String, String> params = queryDecoder.decode(saveMediaQuery);
×
270
        return bynderApi.saveMedia(params).singleOrError().map(RXUtils::getResponseBody);
×
271
    }
272
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc