From b17690ba6d8d33e69fb2270b18ccb28ca57fe4f6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 11:04:29 +0000 Subject: [PATCH] Add download retry mechanism with configurable attempts and timeout Agent-Logs-Url: https://github.com/OpenIdentityPlatform/commons/sessions/fabc1d64-f9e4-46ed-bbc9-c924cc6a7ecd Co-authored-by: vharseko <6818498+vharseko@users.noreply.github.com> --- maven-external-dependency-plugin/README.md | 22 +++ .../maven/plugin/dependency/ArtifactItem.java | 65 ++++++- .../ResolveExternalDependencyMojo.java | 184 ++++++++++++++++-- 3 files changed, 255 insertions(+), 16 deletions(-) diff --git a/maven-external-dependency-plugin/README.md b/maven-external-dependency-plugin/README.md index 4183d94b8..b8e8e823d 100644 --- a/maven-external-dependency-plugin/README.md +++ b/maven-external-dependency-plugin/README.md @@ -2,3 +2,25 @@ [![Build Status](https://travis-ci.org/openam-org-ru/maven-external-dependency-plugin.svg)](https://travis-ci.org/openam-org-ru/maven-external-dependency-plugin) Forked from https://code.google.com/archive/p/maven-external-dependency-plugin/ + +## Download retry configuration + +To make external artifact downloads resilient to transient network errors +(e.g. `java.net.ConnectException: Connection timed out`), the +`resolve-external` goal retries failed downloads. + +Mojo-level configuration parameters (apply to every `` unless +overridden on the artifact itself): + +| Parameter | Default | Description | +|-------------------------|---------|--------------------------------------------------------------| +| `downloadRetryAttempts` | `5` | Number of attempts before giving up on a single download. | +| `downloadTimeout` | `10000` | Per-attempt connection/transfer timeout in milliseconds. | +| `downloadRetryDelay` | `2000` | Delay in milliseconds between retry attempts. | + +Per-artifact overrides (defined inside ``): `timeout`, +`retryAttempts`, `retryDelay`. When set, they take precedence over the +Mojo-level defaults. + +Authorization failures are not retried; the build fails immediately on +`AuthorizationException`. diff --git a/maven-external-dependency-plugin/maven-external-dependency-plugin/src/main/java/com/savage7/maven/plugin/dependency/ArtifactItem.java b/maven-external-dependency-plugin/maven-external-dependency-plugin/src/main/java/com/savage7/maven/plugin/dependency/ArtifactItem.java index 962a38def..090952692 100644 --- a/maven-external-dependency-plugin/maven-external-dependency-plugin/src/main/java/com/savage7/maven/plugin/dependency/ArtifactItem.java +++ b/maven-external-dependency-plugin/maven-external-dependency-plugin/src/main/java/com/savage7/maven/plugin/dependency/ArtifactItem.java @@ -90,6 +90,24 @@ public class ArtifactItem */ private Integer timeout; + /** + * Per-artifact override for the number of attempts to download this + * artifact in case of transient network failures. When unset, the + * Mojo-level {@code downloadRetryAttempts} parameter (default 5) is used. + * + * @parameter + */ + private Integer retryAttempts; + + /** + * Per-artifact override for the delay in millis between download retry + * attempts. When unset, the Mojo-level {@code downloadRetryDelay} + * parameter (default 2000 ms) is used. + * + * @parameter + */ + private Integer retryDelay; + /** * Packaging type of the artifact to be installed. * @@ -387,7 +405,17 @@ public final void setDownloadUrl(final String downloadUrl) */ public final Integer getTimeout() { - return (timeout==null||timeout<=0)?5000:timeout; + return (timeout==null||timeout<=0)?10000:timeout; + } + + /** + * @return Raw timeout value as configured (may be null) so callers can + * distinguish an explicitly set per-artifact timeout from the + * default fallback returned by {@link #getTimeout()}. + */ + public final Integer getTimeoutRaw() + { + return timeout; } /** @@ -399,6 +427,41 @@ public final void setTimeout(final Integer timeout) this.timeout = timeout; } + /** + * @return Raw retry attempts value as configured (may be null). + */ + public final Integer getRetryAttempts() + { + return retryAttempts; + } + + /** + * @param retryAttempts + * Number of attempts to download the artifact in case of + * transient network failures. + */ + public final void setRetryAttempts(final Integer retryAttempts) + { + this.retryAttempts = retryAttempts; + } + + /** + * @return Raw retry delay value (in millis) as configured (may be null). + */ + public final Integer getRetryDelay() + { + return retryDelay; + } + + /** + * @param retryDelay + * Delay in millis between download retry attempts. + */ + public final void setRetryDelay(final Integer retryDelay) + { + this.retryDelay = retryDelay; + } + /** * @return Packaging. */ diff --git a/maven-external-dependency-plugin/maven-external-dependency-plugin/src/main/java/com/savage7/maven/plugin/dependency/ResolveExternalDependencyMojo.java b/maven-external-dependency-plugin/maven-external-dependency-plugin/src/main/java/com/savage7/maven/plugin/dependency/ResolveExternalDependencyMojo.java index 2748b5c30..b084770c2 100644 --- a/maven-external-dependency-plugin/maven-external-dependency-plugin/src/main/java/com/savage7/maven/plugin/dependency/ResolveExternalDependencyMojo.java +++ b/maven-external-dependency-plugin/maven-external-dependency-plugin/src/main/java/com/savage7/maven/plugin/dependency/ResolveExternalDependencyMojo.java @@ -96,6 +96,31 @@ public class ResolveExternalDependencyMojo extends * @readonly */ private MavenSettingsBuilder mavenSettingsBuilder; + + /** + * Default number of attempts to download an external artifact in case + * of transient network failures (e.g. connection timeouts). Used when + * an {@code } does not define its own {@code retryAttempts}. + * + * @parameter default-value="5" + */ + private Integer downloadRetryAttempts; + + /** + * Default timeout in milliseconds for artifact download attempts. Used + * when an {@code } does not define its own {@code timeout}. + * + * @parameter default-value="10000" + */ + private Integer downloadTimeout; + + /** + * Default delay in milliseconds between download retry attempts. Used + * when an {@code } does not define its own {@code retryDelay}. + * + * @parameter default-value="2000" + */ + private Integer downloadRetryDelay; public void execute() throws MojoExecutionException, MojoFailureException { @@ -183,14 +208,6 @@ public void execute() throws MojoExecutionException, MojoFailureException URL downloadUrl = new URL(artifactItem.getDownloadUrl()); String endPointUrl = downloadUrl.getProtocol() + "://"+ downloadUrl.getAuthority(); Repository repository = new Repository("additonal-configs", endPointUrl); - Wagon wagon = wagonManager.getWagon(downloadUrl.getProtocol()); - if (getLog().isDebugEnabled()) - { - Debug debug = new Debug(); - wagon.addSessionListener(debug); - wagon.addTransferListener(debug); - } - wagon.setTimeout(artifactItem.getTimeout()); Settings settings = mavenSettingsBuilder.buildSettings(); ProxyInfo proxyInfo = null; if (settings != null&& settings.getActiveProxy() != null) @@ -204,13 +221,93 @@ public void execute() throws MojoExecutionException, MojoFailureException proxyInfo.setUserName(settingsProxy.getUsername()); proxyInfo.setPassword(settingsProxy.getPassword()); } - - if (proxyInfo != null) - wagon.connect(repository, wagonManager.getAuthenticationInfo(repository.getId()),proxyInfo); - else - wagon.connect(repository, wagonManager.getAuthenticationInfo(repository.getId())); - - wagon.get(downloadUrl.getPath().substring(1), tempDownloadFile); + + // resolve effective retry / timeout settings: + // per-artifact value (if set) wins over the + // Mojo-level default. + int effectiveAttempts = resolveRetryAttempts(artifactItem); + int effectiveTimeout = resolveTimeout(artifactItem); + long effectiveRetryDelay = resolveRetryDelay(artifactItem); + + Exception lastFailure = null; + for (int attempt = 1; attempt <= effectiveAttempts; attempt++) + { + Wagon wagon = wagonManager.getWagon(downloadUrl.getProtocol()); + if (getLog().isDebugEnabled()) + { + Debug debug = new Debug(); + wagon.addSessionListener(debug); + wagon.addTransferListener(debug); + } + wagon.setTimeout(effectiveTimeout); + try + { + if (proxyInfo != null) + wagon.connect(repository, wagonManager.getAuthenticationInfo(repository.getId()),proxyInfo); + else + wagon.connect(repository, wagonManager.getAuthenticationInfo(repository.getId())); + + wagon.get(downloadUrl.getPath().substring(1), tempDownloadFile); + // success: stop retrying + lastFailure = null; + break; + } + catch (org.apache.maven.wagon.authorization.AuthorizationException ae) + { + // authorization issues are not transient, fail fast + throw ae; + } + catch (Exception ex) + { + lastFailure = ex; + getLog().warn( + "download attempt " + attempt + "/" + effectiveAttempts + + " failed for URL: " + artifactItem.getDownloadUrl() + + " - " + ex.getClass().getName() + ": " + ex.getMessage()); + + // discard partial download before retrying + if (tempDownloadFile.exists() && !tempDownloadFile.delete()) + { + getLog().debug("could not delete partial temp file: " + + tempDownloadFile.getAbsolutePath()); + } + + if (attempt < effectiveAttempts && effectiveRetryDelay > 0) + { + try + { + Thread.sleep(effectiveRetryDelay); + } + catch (InterruptedException ie) + { + Thread.currentThread().interrupt(); + throw new MojoExecutionException( + "Interrupted while waiting to retry download of " + + artifactItem.getDownloadUrl(), ie); + } + } + } + finally + { + try + { + wagon.disconnect(); + } + catch (Exception ignored) + { + getLog().debug("error while disconnecting wagon: " + + ignored.getMessage()); + } + } + } + + if (lastFailure != null) + { + throw new MojoExecutionException( + "Failed to download artifact " + artifactItem.getDownloadUrl() + + " after " + effectiveAttempts + " attempt(s)", + lastFailure); + } }else { FileUtils.copyFile(new File(artifactItem.getDownloadUrl()), tempDownloadFile); } @@ -440,4 +537,61 @@ protected boolean resolveArtifactItem(Artifact artifact) return artifactResolved; } + + /** + * Resolve effective number of download attempts for the given artifact. + * Per-artifact value (if set and positive) takes precedence over the + * Mojo-level {@code downloadRetryAttempts} parameter. Falls back to 5. + */ + private int resolveRetryAttempts(ArtifactItem artifactItem) + { + Integer perArtifact = artifactItem.getRetryAttempts(); + if (perArtifact != null && perArtifact > 0) + { + return perArtifact; + } + if (downloadRetryAttempts != null && downloadRetryAttempts > 0) + { + return downloadRetryAttempts; + } + return 5; + } + + /** + * Resolve effective download timeout (in millis) for the given artifact. + * Per-artifact value (if explicitly set and positive) takes precedence + * over the Mojo-level {@code downloadTimeout} parameter. + */ + private int resolveTimeout(ArtifactItem artifactItem) + { + Integer perArtifact = artifactItem.getTimeoutRaw(); + if (perArtifact != null && perArtifact > 0) + { + return perArtifact; + } + if (downloadTimeout != null && downloadTimeout > 0) + { + return downloadTimeout; + } + return 10000; + } + + /** + * Resolve effective delay (in millis) between retry attempts for the + * given artifact. Per-artifact value (if set and non-negative) takes + * precedence over the Mojo-level {@code downloadRetryDelay} parameter. + */ + private long resolveRetryDelay(ArtifactItem artifactItem) + { + Integer perArtifact = artifactItem.getRetryDelay(); + if (perArtifact != null && perArtifact >= 0) + { + return perArtifact; + } + if (downloadRetryDelay != null && downloadRetryDelay >= 0) + { + return downloadRetryDelay; + } + return 2000L; + } }