Skip to content

Commit 63590e7

Browse files
ehung8383mengyushen
authored andcommitted
refactor(service): refactor dtp service to get c2 config from service
1 parent 5e04837 commit 63590e7

5 files changed

Lines changed: 112 additions & 34 deletions

File tree

extensions/data-transfer/portability-data-transfer-synology/src/main/java/org/datatransferproject/datatransfer/synology/SynologyTransferExtension.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@
2626
import org.datatransferproject.transfer.JobMetadata;
2727
import org.datatransferproject.types.common.models.DataVertical;
2828
import org.datatransferproject.types.transfer.auth.AppCredentials;
29+
import org.datatransferproject.types.transfer.serviceconfig.TransferServiceConfig;
2930

3031
public class SynologyTransferExtension implements TransferExtension {
3132
private static final ImmutableList<DataVertical> SUPPORTED_SERVICES =
3233
ImmutableList.of(PHOTOS, VIDEOS, MEDIA);
3334
private Monitor monitor;
35+
3436
private boolean initialized = false;
3537

3638
private ImmutableMap<DataVertical, Importer> importerMap;
@@ -92,9 +94,15 @@ public void initialize(ExtensionContext context) {
9294
.getRetryingIdempotentImportExecutor(context);
9395
SynologyOAuthTokenManager tokenManager = new SynologyOAuthTokenManager(appCredentials, monitor);
9496
OkHttpClient client = context.getService(OkHttpClient.class);
97+
TransferServiceConfig transferServiceConfig = context.getService(TransferServiceConfig.class);
9598
SynologyDTPService synologyDTPService =
9699
new SynologyDTPService(
97-
monitor, JobMetadata.getExportService(), jobStore, tokenManager, client);
100+
monitor,
101+
transferServiceConfig,
102+
JobMetadata.getExportService(),
103+
jobStore,
104+
tokenManager,
105+
client);
98106
SynologyUploader synologyUploader =
99107
new SynologyUploader(idempotentImportExecutor, monitor, synologyDTPService);
100108

extensions/data-transfer/portability-data-transfer-synology/src/main/java/org/datatransferproject/datatransfer/synology/constant/SynologyConstant.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,5 @@
1919

2020
/** Defines constants for Synology data transfer. */
2121
public interface SynologyConstant {
22-
public static final String C2_BASE_URL = "https://sa.dtp.us.c2.synology.com";
2322
public static final String ALBUM_ITEM_ID_FORMAT = "%s-%s";
2423
}

extensions/data-transfer/portability-data-transfer-synology/src/main/java/org/datatransferproject/datatransfer/synology/service/SynologyDTPService.java

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.io.InputStream;
2626
import java.net.MalformedURLException;
2727
import java.net.URL;
28+
import java.util.Date;
2829
import java.util.Map;
2930
import java.util.UUID;
3031
import okhttp3.FormBody;
@@ -35,14 +36,17 @@
3536
import okhttp3.RequestBody;
3637
import okhttp3.Response;
3738
import org.datatransferproject.api.launcher.Monitor;
38-
import org.datatransferproject.datatransfer.synology.constant.SynologyConstant;
3939
import org.datatransferproject.datatransfer.synology.exceptions.SynologyHttpException;
4040
import org.datatransferproject.datatransfer.synology.exceptions.SynologyImportException;
4141
import org.datatransferproject.datatransfer.synology.exceptions.SynologyMaxRetriesExceededException;
42+
import org.datatransferproject.datatransfer.synology.models.C2Api;
43+
import org.datatransferproject.datatransfer.synology.models.ServiceConfig;
44+
import org.datatransferproject.datatransfer.synology.utils.ServiceConfigParser;
4245
import org.datatransferproject.spi.cloud.storage.JobStore;
4346
import org.datatransferproject.types.common.models.media.MediaAlbum;
4447
import org.datatransferproject.types.common.models.photos.PhotoModel;
4548
import org.datatransferproject.types.common.models.videos.VideoModel;
49+
import org.datatransferproject.types.transfer.serviceconfig.TransferServiceConfig;
4650

4751
/** Service for handling Synology DTP operations. */
4852
public class SynologyDTPService {
@@ -52,11 +56,8 @@ public class SynologyDTPService {
5256
private final OkHttpClient client;
5357
private final JobStore jobStore;
5458
private final SynologyOAuthTokenManager tokenManager;
55-
private final String BASE_URL = SynologyConstant.C2_BASE_URL;
56-
private final String CREATE_ALBUM_URL;
57-
private final String UPLOAD_ITEM_URL;
58-
private final String ADD_ITEM_TO_ALBUM_URL;
59-
private final int MAX_RETRIES = 3;
59+
C2Api c2Api;
60+
ServiceConfig.Retry retryConfig;
6061

6162
/**
6263
* Constructs a new {@code SynologyDTPService} instance.
@@ -69,19 +70,21 @@ public class SynologyDTPService {
6970
*/
7071
public SynologyDTPService(
7172
Monitor monitor,
73+
TransferServiceConfig transferServiceConfig,
7274
String exportingService,
7375
JobStore jobStore,
7476
SynologyOAuthTokenManager tokenManager,
7577
OkHttpClient client) {
78+
ServiceConfig serviceConfig = ServiceConfigParser.parse(transferServiceConfig);
79+
this.c2Api = serviceConfig.getServiceAs("c2", C2Api.class);
80+
this.retryConfig = serviceConfig.getRetry();
81+
7682
this.monitor = monitor;
7783
this.exportingService = exportingService;
7884
this.objectMapper = new ObjectMapper();
7985
this.jobStore = jobStore;
8086
this.tokenManager = tokenManager;
8187
this.client = client;
82-
this.CREATE_ALBUM_URL = BASE_URL + "/import/album";
83-
this.UPLOAD_ITEM_URL = BASE_URL + "/import/item";
84-
this.ADD_ITEM_TO_ALBUM_URL = BASE_URL + "/import/album/item";
8588
}
8689

8790
/**
@@ -110,7 +113,7 @@ public Map<String, Object> createAlbum(MediaAlbum album, UUID jobId) {
110113
monitor.info(() -> "[SynologyImporter] Creating album", album.getName(), jobId);
111114

112115
return (Map<String, Object>)
113-
sendPostRequest(CREATE_ALBUM_URL, builder.build(), jobId).get("data");
116+
sendPostRequest(c2Api.getCreateAlbum(), builder.build(), jobId).get("data");
114117
}
115118

116119
/**
@@ -157,7 +160,8 @@ public Map<String, Object> createPhoto(PhotoModel photo, UUID jobId) {
157160

158161
@SuppressWarnings("unchecked")
159162
Map<String, Object> responseData =
160-
(Map<String, Object>) sendPostRequest(UPLOAD_ITEM_URL, builder.build(), jobId).get("data");
163+
(Map<String, Object>)
164+
sendPostRequest(c2Api.getUploadItem(), builder.build(), jobId).get("data");
161165
return responseData;
162166
}
163167

@@ -205,7 +209,8 @@ public Map<String, Object> createVideo(VideoModel video, UUID jobId) {
205209

206210
@SuppressWarnings("unchecked")
207211
Map<String, Object> responseData =
208-
(Map<String, Object>) sendPostRequest(UPLOAD_ITEM_URL, builder.build(), jobId).get("data");
212+
(Map<String, Object>)
213+
sendPostRequest(c2Api.getUploadItem(), builder.build(), jobId).get("data");
209214
return responseData;
210215
}
211216

@@ -223,7 +228,7 @@ public Map<String, Object> addItemToAlbum(String albumId, String itemId, UUID jo
223228
.add("service", exportingService)
224229
.add("album_id", albumId)
225230
.add("item_id", itemId);
226-
return sendPostRequest(ADD_ITEM_TO_ALBUM_URL, builder.build(), jobId);
231+
return sendPostRequest(c2Api.getAddItemToAlbum(), builder.build(), jobId);
227232
}
228233

229234
@VisibleForTesting
@@ -232,7 +237,7 @@ protected Map<String, Object> sendPostRequest(String url, RequestBody body, UUID
232237
Request.Builder requestBuilder = new Request.Builder().url(url).post(body);
233238

234239
Exception lastException = null;
235-
for (int retry = MAX_RETRIES; retry > 0; retry--) {
240+
for (int retry = retryConfig.getMaxAttempts(); retry > 0; retry--) {
236241
Response response = null;
237242
try {
238243
requestBuilder.header("Authorization", "Bearer " + tokenManager.getAccessToken(jobId));
@@ -274,6 +279,6 @@ protected Map<String, Object> sendPostRequest(String url, RequestBody body, UUID
274279
}
275280
}
276281
throw new SynologyMaxRetriesExceededException(
277-
"Failed to send POST request " + MAX_RETRIES + " times", MAX_RETRIES, lastException);
282+
"Failed to send POST request", retryConfig.getMaxAttempts(), lastException);
278283
}
279284
}

extensions/data-transfer/portability-data-transfer-synology/src/test/java/org/datatransferproject/datatransfer/synology/service/SynologyDTPServiceTest.java

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,16 @@
4747
import okhttp3.ResponseBody;
4848
import okio.Buffer;
4949
import org.datatransferproject.api.launcher.Monitor;
50-
import org.datatransferproject.datatransfer.synology.constant.SynologyConstant;
5150
import org.datatransferproject.datatransfer.synology.exceptions.SynologyImportException;
5251
import org.datatransferproject.datatransfer.synology.exceptions.SynologyMaxRetriesExceededException;
52+
import org.datatransferproject.datatransfer.synology.utils.TestConfigs;
5353
import org.datatransferproject.spi.cloud.storage.JobStore;
5454
import org.datatransferproject.spi.cloud.storage.TemporaryPerJobDataStore.InputStreamWrapper;
5555
import org.datatransferproject.types.common.DownloadableFile;
5656
import org.datatransferproject.types.common.models.media.MediaAlbum;
5757
import org.datatransferproject.types.common.models.photos.PhotoModel;
5858
import org.datatransferproject.types.common.models.videos.VideoModel;
59+
import org.datatransferproject.types.transfer.serviceconfig.TransferServiceConfig;
5960
import org.junit.jupiter.api.BeforeEach;
6061
import org.junit.jupiter.api.Nested;
6162
import org.junit.jupiter.api.Test;
@@ -76,15 +77,22 @@ public class SynologyDTPServiceTest {
7677
private final UUID jobId = UUID.randomUUID();
7778
protected SynologyDTPService dtpService;
7879
@Mock protected Monitor monitor;
80+
@Mock protected TransferServiceConfig transferServiceConfig;
7981
@Mock protected JobStore jobStore;
8082
@Mock protected SynologyOAuthTokenManager tokenManager;
8183
@Captor ArgumentCaptor<RequestBody> requestBodyCaptor;
8284
@Mock private OkHttpClient client;
8385

8486
@BeforeEach
85-
public void setUp() throws Exception {
86-
dtpService = new SynologyDTPService(monitor, exportingService, jobStore, tokenManager, client);
87+
public void setUp() {
8788
lenient().when(tokenManager.getAccessToken(jobId)).thenReturn("mockAccessToken");
89+
lenient()
90+
.when(transferServiceConfig.getServiceConfig())
91+
.thenReturn(TestConfigs.createServiceConfigJson());
92+
93+
dtpService =
94+
new SynologyDTPService(
95+
monitor, transferServiceConfig, exportingService, jobStore, tokenManager, client);
8896
}
8997

9098
@Nested
@@ -121,7 +129,9 @@ public void shouldSendPostRequestWithCorrectFormBody() {
121129
public void shouldThrowExceptionIfSendPostRequestFailed() {
122130
SynologyDTPService spyService = Mockito.spy(dtpService);
123131

124-
doThrow(new SynologyMaxRetriesExceededException("MockException", 3, new Exception("Failed")))
132+
doThrow(
133+
new SynologyMaxRetriesExceededException(
134+
"MockException", TestConfigs.TEST_MAX_ATTEMPTS, new Exception("Failed")))
125135
.when(spyService)
126136
.sendPostRequest(anyString(), any(), any());
127137

@@ -171,7 +181,9 @@ public void shouldSendPostRequestWithCorrectFormBody() {
171181
public void shouldThrowExceptionIfSendPostRequestFailed() {
172182
SynologyDTPService spyService = Mockito.spy(dtpService);
173183

174-
doThrow(new SynologyMaxRetriesExceededException("MockException", 3, new Exception("Failed")))
184+
doThrow(
185+
new SynologyMaxRetriesExceededException(
186+
"MockException", TestConfigs.TEST_MAX_ATTEMPTS, new Exception("Failed")))
175187
.when(spyService)
176188
.sendPostRequest(anyString(), any(), any());
177189

@@ -475,7 +487,7 @@ public void shouldRetryIfGotError() throws IOException {
475487
when(mockCall.execute()).thenReturn(mockResponseFail).thenReturn(mockResponseSuccess);
476488

477489
Map<String, Object> result =
478-
dtpService.sendPostRequest(SynologyConstant.C2_BASE_URL, requestBody, jobId);
490+
dtpService.sendPostRequest(TestConfigs.TEST_C2_BASE_URL, requestBody, jobId);
479491

480492
assertEquals(Map.of("success", true), result);
481493

@@ -497,11 +509,11 @@ public void shouldThrowExceptionIfReachMaxRetries() throws IOException {
497509

498510
assertThrows(
499511
SynologyMaxRetriesExceededException.class,
500-
() -> dtpService.sendPostRequest(SynologyConstant.C2_BASE_URL, requestBody, jobId),
501-
"Failed to send POST request 3 times");
512+
() -> dtpService.sendPostRequest(TestConfigs.TEST_C2_BASE_URL, requestBody, jobId),
513+
String.format("Failed to send POST request %d times", TestConfigs.TEST_MAX_ATTEMPTS));
502514
verify(tokenManager, never())
503515
.refreshToken(any(UUID.class), eq(client), any(ObjectMapper.class));
504-
verify(client, times(3)).newCall(any(Request.class));
516+
verify(client, times(TestConfigs.TEST_MAX_ATTEMPTS)).newCall(any(Request.class));
505517
}
506518

507519
@Test
@@ -525,7 +537,7 @@ public void shouldRefreshTokenAndRetryIfGotUnauthorizedHttpError()
525537
.thenReturn(true);
526538

527539
Map<String, Object> result =
528-
dtpService.sendPostRequest(SynologyConstant.C2_BASE_URL, requestBody, jobId);
540+
dtpService.sendPostRequest(TestConfigs.TEST_C2_BASE_URL, requestBody, jobId);
529541

530542
assertEquals(Map.of("success", true), result);
531543

@@ -550,12 +562,12 @@ public void shouldInvokeRefreshTokenOnlyOnceIfGotUnauthorizedMultipleTimes()
550562

551563
assertThrows(
552564
SynologyMaxRetriesExceededException.class,
553-
() -> dtpService.sendPostRequest(SynologyConstant.C2_BASE_URL, requestBody, jobId),
554-
"Failed to send POST request 3 times");
565+
() -> dtpService.sendPostRequest(TestConfigs.TEST_C2_BASE_URL, requestBody, jobId),
566+
String.format("Failed to send POST request %d times", TestConfigs.TEST_MAX_ATTEMPTS));
555567

556568
verify(tokenManager, times(1))
557569
.refreshToken(any(UUID.class), eq(client), any(ObjectMapper.class));
558-
verify(client, times(3)).newCall(any(Request.class));
570+
verify(client, times(TestConfigs.TEST_MAX_ATTEMPTS)).newCall(any(Request.class));
559571
}
560572

561573
@Test
@@ -573,11 +585,11 @@ public void shouldThrowExceptionIfParseResponseFailed() throws IOException {
573585

574586
assertThrows(
575587
SynologyMaxRetriesExceededException.class,
576-
() -> dtpService.sendPostRequest(SynologyConstant.C2_BASE_URL, requestBody, jobId),
577-
"Failed to send POST request 3 times");
588+
() -> dtpService.sendPostRequest(TestConfigs.TEST_C2_BASE_URL, requestBody, jobId),
589+
String.format("Failed to send POST request %d times", TestConfigs.TEST_MAX_ATTEMPTS));
578590
verify(tokenManager, never())
579591
.refreshToken(any(UUID.class), eq(client), any(ObjectMapper.class));
580-
verify(client, times(3)).newCall(any(Request.class));
592+
verify(client, times(TestConfigs.TEST_MAX_ATTEMPTS)).newCall(any(Request.class));
581593
}
582594

583595
@Test
@@ -593,7 +605,7 @@ public void shouldReturnResponseData() throws IOException, JsonProcessingExcepti
593605
when(mockResponseBody.string()).thenReturn("{\"success\": true}");
594606

595607
Map<String, Object> result =
596-
dtpService.sendPostRequest(SynologyConstant.C2_BASE_URL, requestBody, jobId);
608+
dtpService.sendPostRequest(TestConfigs.TEST_C2_BASE_URL, requestBody, jobId);
597609
assertEquals(Map.of("success", true), result);
598610
verify(tokenManager, never())
599611
.refreshToken(any(UUID.class), eq(client), any(ObjectMapper.class));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2025 The Data Transfer Project Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
package org.datatransferproject.datatransfer.synology.utils;
19+
20+
import com.fasterxml.jackson.databind.JsonNode;
21+
import com.fasterxml.jackson.databind.ObjectMapper;
22+
import java.util.HashMap;
23+
import java.util.Map;
24+
import java.util.Optional;
25+
import org.datatransferproject.datatransfer.synology.models.C2Api;
26+
import org.datatransferproject.datatransfer.synology.models.ServiceConfig;
27+
28+
public class TestConfigs {
29+
public static final String TEST_C2_BASE_URL = "https://fake.url";
30+
public static final String TEST_CREATE_ALBUM_PATH = "/create";
31+
public static final String TEST_UPLOAD_ITEM_PATH = "/upload";
32+
public static final String TEST_ADD_ITEM_TO_ALBUM_PATH = "/add";
33+
public static final int TEST_MAX_ATTEMPTS = 5;
34+
35+
public static ServiceConfig createServiceConfig() {
36+
C2Api.ApiPath apiPath =
37+
new C2Api.ApiPath(
38+
TEST_CREATE_ALBUM_PATH, TEST_UPLOAD_ITEM_PATH, TEST_ADD_ITEM_TO_ALBUM_PATH);
39+
40+
C2Api c2Api = new C2Api(TEST_C2_BASE_URL, apiPath);
41+
42+
Map<String, ServiceConfig.Service> serviceMap = new HashMap<>();
43+
serviceMap.put("c2", c2Api);
44+
45+
ServiceConfig.Retry retry = new ServiceConfig.Retry(TEST_MAX_ATTEMPTS);
46+
47+
return new ServiceConfig(retry, serviceMap);
48+
}
49+
50+
public static Optional<JsonNode> createServiceConfigJson() {
51+
ObjectMapper mapper = new ObjectMapper();
52+
return Optional.of(mapper.valueToTree(createServiceConfig()));
53+
}
54+
}

0 commit comments

Comments
 (0)