Skip to content

Commit b7e4d89

Browse files
DGarbarrpanchyk
authored andcommitted
Add autoupdate in RemoteFileSyncer (prebid#478)
1 parent 5b416e2 commit b7e4d89

File tree

6 files changed

+380
-149
lines changed

6 files changed

+380
-149
lines changed

docs/config-app.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,12 @@ Remote File Syncer can be related to particular entity like geolocation maxmind
4040
Removes and downloads file again if depending service cant process probably corrupted file in the first start.
4141

4242
- `<SERVICE>.remote-file-syncer.download-url` - url to database file to download.
43-
- `<SERVICE>.remote-file-syncer.save-filepath` - local path to downloaded database file.
43+
- `<SERVICE>.remote-file-syncer.save-filepath` - full path to the usable file, which will be consumed by internal service.
44+
- `<SERVICE>.remote-file-syncer.tmp-filepath` - full path to the temporary file.
4445
- `<SERVICE>.remote-file-syncer.retry-count` - how many times try to download.
4546
- `<SERVICE>.remote-file-syncer.retry-interval-ms` - how long to wait between failed retries.
4647
- `<SERVICE>.remote-file-syncer.timeout-ms` - default operation timeout for obtaining database file.
48+
- `<SERVICE>.remote-file-syncer.update-interval-ms` - time interval between updates of the usable file.
4749
- `<SERVICE>.remote-file-syncer.http-client.connect-timeout-ms` - set the connect timeout.
4850
- `<SERVICE>.remote-file-syncer.http-client.max-redirects` - set the maximum amount of HTTP redirections to follow. A value of 0 (the default) prevents redirections from being followed.
4951

src/main/java/org/prebid/server/execution/RemoteFileSyncer.java

Lines changed: 156 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@
44
import io.vertx.core.Future;
55
import io.vertx.core.Vertx;
66
import io.vertx.core.file.AsyncFile;
7+
import io.vertx.core.file.CopyOptions;
78
import io.vertx.core.file.FileProps;
89
import io.vertx.core.file.FileSystem;
910
import io.vertx.core.file.FileSystemException;
1011
import io.vertx.core.file.OpenOptions;
1112
import io.vertx.core.http.HttpClient;
12-
import io.vertx.core.http.HttpClientRequest;
1313
import io.vertx.core.http.HttpClientResponse;
14+
import io.vertx.core.http.HttpHeaders;
1415
import io.vertx.core.logging.Logger;
1516
import io.vertx.core.logging.LoggerFactory;
1617
import io.vertx.core.streams.Pump;
18+
import org.apache.commons.lang3.StringUtils;
1719
import org.prebid.server.exception.PreBidException;
1820
import org.prebid.server.util.HttpUtil;
1921

@@ -32,38 +34,45 @@ public class RemoteFileSyncer {
3234

3335
private final String downloadUrl; // url to resource to be downloaded
3436
private final String saveFilePath; // full path on file system where downloaded file located
37+
private final String tmpFilePath; // full path on file system where tmp file located
3538
private final int retryCount; // how many times try to download
3639
private final long retryInterval; // how long to wait between failed retries
3740
private final long timeout;
41+
private final long updatePeriod;
3842
private final HttpClient httpClient;
3943
private final Vertx vertx;
4044
private final FileSystem fileSystem;
4145

42-
private RemoteFileSyncer(String downloadUrl, String saveFilePath, int retryCount,
43-
long retryInterval, long timeout, HttpClient httpClient, Vertx vertx,
46+
private RemoteFileSyncer(String downloadUrl, String saveFilePath, String tmpFilePath, int retryCount,
47+
long retryInterval, long timeout, long updatePeriod, HttpClient httpClient, Vertx vertx,
4448
FileSystem fileSystem) {
4549
this.downloadUrl = downloadUrl;
4650
this.saveFilePath = saveFilePath;
51+
this.tmpFilePath = tmpFilePath;
4752
this.retryCount = retryCount;
4853
this.retryInterval = retryInterval;
4954
this.timeout = timeout;
55+
this.updatePeriod = updatePeriod;
5056
this.httpClient = httpClient;
5157
this.vertx = vertx;
5258
this.fileSystem = fileSystem;
5359
}
5460

55-
public static RemoteFileSyncer create(String downloadUrl, String saveFilePath, int retryCount, long retryInterval,
56-
long timeout, HttpClient httpClient, Vertx vertx) {
61+
public static RemoteFileSyncer create(String downloadUrl, String saveFilePath, String tmpFilePath, int retryCount,
62+
long retryInterval, long timeout, long updatePeriod, HttpClient httpClient,
63+
Vertx vertx, FileSystem fileSystem) {
5764
HttpUtil.validateUrl(downloadUrl);
5865
Objects.requireNonNull(saveFilePath);
66+
Objects.requireNonNull(tmpFilePath);
5967
Objects.requireNonNull(vertx);
6068
Objects.requireNonNull(httpClient);
61-
final FileSystem fileSystem = vertx.fileSystem();
69+
Objects.requireNonNull(fileSystem);
6270

6371
createAndCheckWritePermissionsFor(fileSystem, saveFilePath);
72+
createAndCheckWritePermissionsFor(fileSystem, tmpFilePath);
6473

65-
return new RemoteFileSyncer(downloadUrl, saveFilePath, retryCount, retryInterval, timeout, httpClient, vertx,
66-
fileSystem);
74+
return new RemoteFileSyncer(downloadUrl, saveFilePath, tmpFilePath, retryCount, retryInterval, timeout,
75+
updatePeriod, httpClient, vertx, fileSystem);
6776
}
6877

6978
/**
@@ -90,36 +99,68 @@ public void syncForFilepath(RemoteFileProcessor remoteFileProcessor) {
9099
downloadIfNotExist(remoteFileProcessor).setHandler(syncResult -> handleSync(remoteFileProcessor, syncResult));
91100
}
92101

93-
private Future<Void> downloadIfNotExist(RemoteFileProcessor fileProcessor) {
94-
final Future<Void> future = Future.future();
95-
fileSystem.exists(saveFilePath, existResult -> handleFileExisting(future, existResult, fileProcessor));
102+
private Future<Boolean> downloadIfNotExist(RemoteFileProcessor fileProcessor) {
103+
final Future<Boolean> future = Future.future();
104+
checkFileExist(saveFilePath).setHandler(existResult ->
105+
handleFileExistingWithSync(existResult, fileProcessor, future));
96106
return future;
97107
}
98108

99-
private void handleFileExisting(Future<Void> future, AsyncResult<Boolean> existResult,
100-
RemoteFileProcessor fileProcessor) {
109+
private Future<Boolean> checkFileExist(String filePath) {
110+
final Future<Boolean> result = Future.future();
111+
fileSystem.exists(filePath, async -> {
112+
if (async.succeeded()) {
113+
result.complete(async.result());
114+
} else {
115+
result.fail(String.format("Cant check if file exists %s", filePath));
116+
}
117+
});
118+
return result;
119+
}
120+
121+
private void handleFileExistingWithSync(AsyncResult<Boolean> existResult, RemoteFileProcessor fileProcessor,
122+
Future<Boolean> future) {
101123
if (existResult.succeeded()) {
102124
if (existResult.result()) {
103125
fileProcessor.setDataPath(saveFilePath)
104126
.setHandler(serviceRespond -> handleServiceRespond(serviceRespond, future));
105127
} else {
106-
tryDownload(future);
128+
syncRemoteFiles().setHandler(future);
107129
}
108130
} else {
109131
future.fail(existResult.cause());
110132
}
111133
}
112134

113-
private void handleServiceRespond(AsyncResult<?> processResult, Future<Void> future) {
135+
private void handleServiceRespond(AsyncResult<?> processResult, Future<Boolean> future) {
114136
if (processResult.failed()) {
115137
final Throwable cause = processResult.cause();
116-
cleanUp().setHandler(removalResult -> handleCorruptedFileRemoval(removalResult, future, cause));
138+
cleanUp(saveFilePath).setHandler(removalResult -> handleCorruptedFileRemoval(removalResult, future, cause));
117139
} else {
140+
future.complete(false);
118141
logger.info("Existing file {0} was successfully reused for service", saveFilePath);
119142
}
120143
}
121144

122-
private void handleCorruptedFileRemoval(AsyncResult<Void> removalResult, Future<Void> future,
145+
private Future<Void> cleanUp(String filePath) {
146+
final Future<Void> future = Future.future();
147+
checkFileExist(filePath).setHandler(existResult -> handleFileExistsWithDelete(filePath, existResult, future));
148+
return future;
149+
}
150+
151+
private void handleFileExistsWithDelete(String filePath, AsyncResult<Boolean> existResult, Future<Void> future) {
152+
if (existResult.succeeded()) {
153+
if (existResult.result()) {
154+
fileSystem.delete(filePath, future);
155+
} else {
156+
future.complete();
157+
}
158+
} else {
159+
future.fail(new PreBidException(String.format("Cant check if file exists %s", filePath)));
160+
}
161+
}
162+
163+
private void handleCorruptedFileRemoval(AsyncResult<Void> removalResult, Future<Boolean> future,
123164
Throwable serviceCause) {
124165
if (removalResult.failed()) {
125166
final Throwable cause = removalResult.cause();
@@ -130,29 +171,42 @@ private void handleCorruptedFileRemoval(AsyncResult<Void> removalResult, Future<
130171
logger.info("Existing file {0} cant be processed by service, try to download after removal",
131172
serviceCause, saveFilePath);
132173

133-
tryDownload(future);
174+
syncRemoteFiles().setHandler(future);
134175
}
135176
}
136177

137-
private void tryDownload(Future<Void> future) {
138-
download().setHandler(downloadResult -> handleDownload(future, downloadResult));
178+
private Future<Boolean> syncRemoteFiles() {
179+
return tryDownload()
180+
.compose(downloadResult -> swapFiles())
181+
.map(true);
182+
}
183+
184+
private Future<Void> tryDownload() {
185+
final Future<Void> result = Future.future();
186+
cleanUp(tmpFilePath).setHandler(event -> handleTmpDelete(event, result));
187+
return result;
188+
}
189+
190+
private void handleTmpDelete(AsyncResult<Void> tmpDeleteResult, Future<Void> result) {
191+
if (tmpDeleteResult.failed()) {
192+
result.fail(tmpDeleteResult.cause());
193+
} else {
194+
download().setHandler(downloadResult -> handleDownload(downloadResult, result));
195+
}
139196
}
140197

141198
private Future<Void> download() {
142199
final Future<Void> future = Future.future();
143200
final OpenOptions openOptions = new OpenOptions().setCreateNew(true);
144-
fileSystem.open(saveFilePath, openOptions, openResult -> handleFileOpenWithDownload(future, openResult));
201+
fileSystem.open(tmpFilePath, openOptions, openResult -> handleFileOpenWithDownload(openResult, future));
145202
return future;
146203
}
147204

148-
private void handleFileOpenWithDownload(Future<Void> future, AsyncResult<AsyncFile> openResult) {
205+
private void handleFileOpenWithDownload(AsyncResult<AsyncFile> openResult, Future<Void> future) {
149206
if (openResult.succeeded()) {
150207
final AsyncFile asyncFile = openResult.result();
151208
try {
152-
// .getNow is not working
153-
final HttpClientRequest httpClientRequest = httpClient
154-
.getAbs(downloadUrl, response -> pumpFileFromRequest(response, asyncFile, future));
155-
httpClientRequest.end();
209+
httpClient.getAbs(downloadUrl, response -> pumpFileFromRequest(response, asyncFile, future)).end();
156210
} catch (Exception e) {
157211
future.fail(e);
158212
}
@@ -162,35 +216,35 @@ private void handleFileOpenWithDownload(Future<Void> future, AsyncResult<AsyncFi
162216
}
163217

164218
private void pumpFileFromRequest(HttpClientResponse httpClientResponse, AsyncFile asyncFile, Future<Void> future) {
165-
logger.info("Trying to download from {0}", downloadUrl);
219+
logger.info("Trying to download file from {0}", downloadUrl);
166220
httpClientResponse.pause();
167221
final Pump pump = Pump.pump(httpClientResponse, asyncFile);
168222
pump.start();
169223
httpClientResponse.resume();
170224

171-
final long idTimer = setTimeoutTimer(asyncFile, future, pump);
225+
final long idTimer = setTimeoutTimer(asyncFile, pump, future);
172226

173-
httpClientResponse.endHandler(responseEndResult -> handleResponseEnd(asyncFile, future, idTimer));
227+
httpClientResponse.endHandler(responseEndResult -> handleResponseEnd(asyncFile, idTimer, future));
174228
}
175229

176-
private long setTimeoutTimer(AsyncFile asyncFile, Future<Void> future, Pump pump) {
177-
return vertx.setTimer(timeout, timerId -> handleTimeout(asyncFile, future, pump));
230+
private long setTimeoutTimer(AsyncFile asyncFile, Pump pump, Future<Void> future) {
231+
return vertx.setTimer(timeout, timerId -> handleTimeout(asyncFile, pump, future));
178232
}
179233

180-
private void handleTimeout(AsyncFile asyncFile, Future<Void> future, Pump pump) {
234+
private void handleTimeout(AsyncFile asyncFile, Pump pump, Future<Void> future) {
181235
pump.stop();
182236
asyncFile.close();
183237
if (!future.isComplete()) {
184238
future.fail(new TimeoutException("Timeout on download"));
185239
}
186240
}
187241

188-
private void handleResponseEnd(AsyncFile asyncFile, Future<Void> future, long idTimer) {
242+
private void handleResponseEnd(AsyncFile asyncFile, long idTimer, Future<Void> future) {
189243
vertx.cancelTimer(idTimer);
190244
asyncFile.flush().close(future);
191245
}
192246

193-
private void handleDownload(Future<Void> future, AsyncResult<Void> downloadResult) {
247+
private void handleDownload(AsyncResult<Void> downloadResult, Future<Void> future) {
194248
if (downloadResult.failed()) {
195249
retryDownload(future, retryInterval, retryCount);
196250
} else {
@@ -206,45 +260,47 @@ private void retryDownload(Future<Void> receivedFuture, long retryInterval, long
206260
private void handleRetry(Future<Void> receivedFuture, long retryInterval, long retryCount) {
207261
if (retryCount > 0) {
208262
final long next = retryCount - 1;
209-
cleanUp().compose(aVoid -> download())
210-
.setHandler(retryResult -> handleRetryResult(receivedFuture, retryInterval, next, retryResult));
263+
cleanUp(tmpFilePath).compose(ignore -> download())
264+
.setHandler(retryResult -> handleRetryResult(retryInterval, next, retryResult, receivedFuture));
211265
} else {
212-
cleanUp().setHandler(aVoid -> receivedFuture.fail(new PreBidException("File sync failed after retries")));
266+
cleanUp(tmpFilePath).setHandler(ignore -> receivedFuture.fail(new PreBidException(
267+
String.format("File sync failed after %s retries", this.retryCount - retryCount))));
213268
}
214269
}
215270

216-
private Future<Void> cleanUp() {
217-
final Future<Void> future = Future.future();
218-
fileSystem.exists(saveFilePath, existResult -> handleFileExistsWithDelete(future, existResult));
219-
return future;
220-
}
221-
222-
private void handleFileExistsWithDelete(Future<Void> future, AsyncResult<Boolean> existResult) {
223-
if (existResult.succeeded()) {
224-
if (existResult.result()) {
225-
fileSystem.delete(saveFilePath, future);
226-
} else {
227-
future.complete();
228-
}
229-
} else {
230-
future.fail(new PreBidException(String.format("Cant check if file exists %s", saveFilePath)));
231-
}
232-
}
233-
234-
private void handleRetryResult(Future<Void> future, long retryInterval, long next, AsyncResult<Void> retryResult) {
271+
private void handleRetryResult(long retryInterval, long next, AsyncResult<Void> retryResult, Future<Void> future) {
235272
if (retryResult.succeeded()) {
236273
future.complete();
237274
} else {
238275
retryDownload(future, retryInterval, next);
239276
}
240277
}
241278

242-
private void handleSync(RemoteFileProcessor remoteFileProcessor, AsyncResult<Void> syncResult) {
279+
private Future<Void> swapFiles() {
280+
final Future<Void> result = Future.future();
281+
logger.info("Sync {0} to {1}", tmpFilePath, saveFilePath);
282+
283+
final CopyOptions copyOptions = new CopyOptions().setReplaceExisting(true);
284+
fileSystem.move(tmpFilePath, saveFilePath, copyOptions, result);
285+
return result;
286+
}
287+
288+
private void handleSync(RemoteFileProcessor remoteFileProcessor, AsyncResult<Boolean> syncResult) {
243289
if (syncResult.succeeded()) {
244-
remoteFileProcessor.setDataPath(saveFilePath)
245-
.setHandler(this::logFileProcessStatus);
290+
if (syncResult.result()) {
291+
logger.info("Sync service for {0}", saveFilePath);
292+
remoteFileProcessor.setDataPath(saveFilePath)
293+
.setHandler(this::logFileProcessStatus);
294+
} else {
295+
logger.info("Sync is not required for {0}", saveFilePath);
296+
}
246297
} else {
247-
logger.error("Cant download file from {0}", syncResult.cause(), downloadUrl);
298+
logger.error("Cant sync file from {0}", syncResult.cause(), downloadUrl);
299+
}
300+
301+
// setup new update regardless of the result
302+
if (updatePeriod > 0) {
303+
vertx.setTimer(updatePeriod, idUpdateNew -> configureAutoUpdates(remoteFileProcessor));
248304
}
249305
}
250306

@@ -255,5 +311,45 @@ private void logFileProcessStatus(AsyncResult<?> serviceRespond) {
255311
logger.error("Service cant process file {0} and still unavailable.", saveFilePath);
256312
}
257313
}
314+
315+
private void configureAutoUpdates(RemoteFileProcessor remoteFileProcessor) {
316+
logger.info("Check for updated for {0}", saveFilePath);
317+
tryUpdate().setHandler(asyncUpdate -> {
318+
if (asyncUpdate.failed()) {
319+
logger.warn("File {0} update failed", asyncUpdate.cause(), saveFilePath);
320+
}
321+
handleSync(remoteFileProcessor, asyncUpdate);
322+
});
323+
}
324+
325+
private Future<Boolean> tryUpdate() {
326+
return checkFileExist(saveFilePath)
327+
.compose(fileExists -> fileExists ? isNeedToUpdate() : Future.succeededFuture(true))
328+
.compose(needUpdate -> needUpdate ? syncRemoteFiles() : Future.succeededFuture(false));
329+
}
330+
331+
private Future<Boolean> isNeedToUpdate() {
332+
final Future<Boolean> isNeedToUpdate = Future.future();
333+
httpClient.headAbs(downloadUrl, response -> checkNewVersion(response, isNeedToUpdate))
334+
.exceptionHandler(isNeedToUpdate::fail)
335+
.end();
336+
return isNeedToUpdate;
337+
}
338+
339+
private void checkNewVersion(HttpClientResponse response, Future<Boolean> isNeedToUpdate) {
340+
final String contentLengthParameter = response.getHeader(HttpHeaders.CONTENT_LENGTH);
341+
if (StringUtils.isNumeric(contentLengthParameter) && !contentLengthParameter.equals("0")) {
342+
final long contentLength = Long.parseLong(contentLengthParameter);
343+
fileSystem.props(saveFilePath, filePropsResult -> {
344+
if (filePropsResult.succeeded()) {
345+
isNeedToUpdate.complete(filePropsResult.result().size() != contentLength);
346+
} else {
347+
isNeedToUpdate.fail(filePropsResult.cause());
348+
}
349+
});
350+
} else {
351+
isNeedToUpdate.fail(String.format("ContentLength is invalid: %s", contentLengthParameter));
352+
}
353+
}
258354
}
259355

src/main/java/org/prebid/server/spring/config/GeoLocationConfiguration.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,10 @@ private GeoLocationService createGeoLocationService(RemoteFileSyncerProperties f
7575
.setMaxRedirects(httpClientProperties.getMaxRedirects());
7676

7777
final RemoteFileSyncer remoteFileSyncer = RemoteFileSyncer.create(fileSyncerProperties.getDownloadUrl(),
78-
fileSyncerProperties.getSaveFilepath(), fileSyncerProperties.getRetryCount(),
79-
fileSyncerProperties.getRetryIntervalMs(), fileSyncerProperties.getTimeoutMs(),
80-
vertx.createHttpClient(httpClientOptions), vertx);
78+
fileSyncerProperties.getSaveFilepath(), fileSyncerProperties.getTmpFilepath(),
79+
fileSyncerProperties.getRetryCount(), fileSyncerProperties.getRetryIntervalMs(),
80+
fileSyncerProperties.getTimeoutMs(), fileSyncerProperties.getUpdateIntervalMs(),
81+
vertx.createHttpClient(httpClientOptions), vertx, vertx.fileSystem());
8182
final MaxMindGeoLocationService maxMindGeoLocationService = new MaxMindGeoLocationService();
8283

8384
remoteFileSyncer.syncForFilepath(maxMindGeoLocationService);

0 commit comments

Comments
 (0)