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

devonfw / IDEasy / 20271179405

16 Dec 2025 02:21PM UTC coverage: 70.061% (-0.08%) from 70.142%
20271179405

push

github

web-flow
#1660: status robustness #1475: fix tests to work offline (#1661)

3965 of 6233 branches covered (63.61%)

Branch coverage included in aggregate %.

10162 of 13931 relevant lines covered (72.95%)

3.15 hits per line

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

70.66
cli/src/main/java/com/devonfw/tools/ide/tool/repository/MvnRepository.java
1
package com.devonfw.tools.ide.tool.repository;
2

3
import java.io.InputStream;
4
import java.net.URL;
5
import java.nio.file.Files;
6
import java.nio.file.Path;
7
import java.time.Duration;
8
import java.util.ArrayList;
9
import java.util.Iterator;
10
import java.util.List;
11
import java.util.Locale;
12
import java.util.Map;
13
import javax.xml.parsers.DocumentBuilder;
14
import javax.xml.parsers.DocumentBuilderFactory;
15

16
import org.w3c.dom.Document;
17
import org.w3c.dom.Element;
18
import org.w3c.dom.Node;
19
import org.w3c.dom.NodeList;
20

21
import com.devonfw.tools.ide.cli.CliException;
22
import com.devonfw.tools.ide.context.IdeContext;
23
import com.devonfw.tools.ide.os.OperatingSystem;
24
import com.devonfw.tools.ide.os.SystemArchitecture;
25
import com.devonfw.tools.ide.tool.IdeasyCommandlet;
26
import com.devonfw.tools.ide.tool.ToolCommandlet;
27
import com.devonfw.tools.ide.tool.mvn.MvnArtifact;
28
import com.devonfw.tools.ide.tool.mvn.MvnBasedLocalToolCommandlet;
29
import com.devonfw.tools.ide.url.model.file.UrlChecksums;
30
import com.devonfw.tools.ide.url.model.file.UrlDownloadFileMetadata;
31
import com.devonfw.tools.ide.url.model.file.UrlGenericChecksum;
32
import com.devonfw.tools.ide.url.model.file.UrlGenericChecksumType;
33
import com.devonfw.tools.ide.variable.IdeVariables;
34
import com.devonfw.tools.ide.version.GenericVersionRange;
35
import com.devonfw.tools.ide.version.VersionIdentifier;
36

37
/**
38
 * Implementation of {@link AbstractToolRepository} for {@link com.devonfw.tools.ide.tool.java.Java java}-based artifacts. Actually
39
 * {@link com.devonfw.tools.ide.tool.mvn.Mvn maven} was the first famous build management tool with a central repository. Meanwhile, there are others like
40
 * {@link com.devonfw.tools.ide.tool.gradle.Gradle}. However, it is still called maven-repository and not java-repository.
41
 */
42
public class MvnRepository extends ArtifactToolRepository<MvnArtifact, MvnArtifactMetadata> {
43

44
  /** Base URL for Maven Central repository */
45
  public static final String MAVEN_CENTRAL = "https://repo1.maven.org/maven2";
46

47
  /** Base URL for Maven Snapshots repository */
48
  public static final String MAVEN_SNAPSHOTS = "https://central.sonatype.com/repository/maven-snapshots";
49

50
  /** The {@link #getId() repository ID}. */
51
  public static final String ID = "maven";
52

53
  private static final Duration METADATA_CACHE_DURATION_RELEASE = Duration.ofHours(1);
3✔
54

55
  private static final Duration METADATA_CACHE_DURATION_SNAPSHOT = Duration.ofMinutes(5);
3✔
56

57
  private final Path localMavenRepository;
58

59
  private final DocumentBuilder documentBuilder;
60

61
  private static final Map<String, MvnArtifact> TOOL_MAP = Map.of(
12✔
62
      "ideasy", IdeasyCommandlet.ARTIFACT,
63
      "gcviewer", new MvnArtifact("com.github.chewiebug", "gcviewer", "*")
64
  );
65

66
  /**
67
   * The constructor.
68
   *
69
   * @param context the owning {@link IdeContext}.
70
   */
71
  public MvnRepository(IdeContext context) {
72

73
    super(context);
3✔
74
    this.localMavenRepository = IdeVariables.M2_REPO.get(this.context);
7✔
75
    try {
76
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
2✔
77
      this.documentBuilder = factory.newDocumentBuilder();
4✔
78
    } catch (Exception e) {
×
79
      throw new IllegalStateException("Failed to create XML document builder", e);
×
80
    }
1✔
81
  }
1✔
82

83
  @Override
84
  public String getId() {
85

86
    return ID;
2✔
87
  }
88

89
  @Override
90
  protected MvnArtifact resolveArtifact(String tool, String edition, VersionIdentifier version, ToolCommandlet toolCommandlet) {
91
    MvnArtifact artifact;
92
    if (toolCommandlet instanceof MvnBasedLocalToolCommandlet mvnBasedTool) {
6✔
93
      artifact = mvnBasedTool.getArtifact(edition);
5✔
94
    } else {
95
      String key = tool;
2✔
96
      if (!tool.equals(edition)) {
4!
97
        key = tool + ":" + edition;
×
98
      }
99
      artifact = TOOL_MAP.get(key);
5✔
100
      if (artifact == null) {
2!
101
        throw new UnsupportedOperationException("Tool '" + key + "' is not supported by Maven repository.");
×
102
      }
103
    }
104
    if (version != null) {
2✔
105
      artifact = artifact.withVersion(version.toString());
5✔
106
    }
107
    return artifact;
2✔
108
  }
109

110
  @Override
111
  public MvnArtifactMetadata getMetadata(MvnArtifact artifact, String tool, String edition) {
112
    OperatingSystem os = null;
2✔
113
    SystemArchitecture arch = null;
2✔
114
    String classifier = artifact.getClassifier();
3✔
115
    if (!classifier.isEmpty()) {
3!
116
      String resolvedClassifier;
117
      os = this.context.getSystemInfo().getOs();
5✔
118
      resolvedClassifier = classifier.replace("${os}", os.toString());
6✔
119
      if (resolvedClassifier.equals(classifier)) {
4✔
120
        os = null;
3✔
121
      } else {
122
        classifier = resolvedClassifier;
2✔
123
      }
124
      arch = this.context.getSystemInfo().getArchitecture();
5✔
125
      resolvedClassifier = classifier.replace("${arch}", arch.toString());
6✔
126
      if (resolvedClassifier.equals(classifier)) {
4✔
127
        arch = null;
2✔
128
      }
129
      artifact = artifact.withClassifier(resolvedClassifier);
4✔
130
    }
131
    UrlChecksums checksums = getChecksums(artifact);
4✔
132
    return new MvnArtifactMetadata(artifact, tool, edition, checksums, os, arch);
10✔
133
  }
134

135
  /**
136
   * Method is required to disable checksum checks in tests.
137
   *
138
   * @param artifact the {@link MvnArtifact} to use.
139
   * @return the {@link UrlChecksums}.
140
   */
141
  protected UrlChecksums getChecksums(MvnArtifact artifact) {
142

143
    UrlChecksums checksums = null;
2✔
144
    if (!artifact.isMavenMetadata()) {
3!
145
      checksums = new UrlLazyChecksums(artifact);
6✔
146
    }
147
    return checksums;
2✔
148
  }
149

150
  protected UrlGenericChecksum getChecksum(MvnArtifact artifact, String hashAlgorithm) {
151

152
    MvnArtifact checksumArtifact = artifact.withType(artifact.getType() + "." + hashAlgorithm.toLowerCase(Locale.ROOT));
×
153
    Path checksumFile = getDownloadedArtifact(checksumArtifact, null);
×
154
    String checksum = this.context.getFileAccess().readFileContent(checksumFile).trim();
×
155
    return new UrlGenericChecksumType(checksum, hashAlgorithm, checksumFile);
×
156
  }
157

158
  private Path getDownloadedArtifact(MvnArtifact artifact, UrlChecksums checksums) {
159

160
    Path file = this.localMavenRepository.resolve(artifact.getPath());
6✔
161
    if (isNotUpToDateInLocalRepo(file)) {
4!
162
      this.context.getFileAccess().mkdirs(file.getParent());
6✔
163
      download(getMavenUrl(artifact), file, artifact.getVersion(), checksums);
10✔
164
    }
165
    return file;
2✔
166
  }
167

168
  private boolean isNotUpToDateInLocalRepo(Path file) {
169
    if (!Files.exists(file)) {
5!
170
      return true;
2✔
171
    }
172
    if (file.getFileName().toString().equals(MvnArtifact.MAVEN_METADATA_XML)) {
×
173
      Duration cacheDuration = METADATA_CACHE_DURATION_RELEASE;
×
174
      if (file.getParent().getFileName().toString().endsWith("-SNAPSHOT")) {
×
175
        cacheDuration = METADATA_CACHE_DURATION_SNAPSHOT;
×
176
      }
177
      return !this.context.getFileAccess().isFileAgeRecent(file, cacheDuration);
×
178
    }
179
    return false;
×
180
  }
181

182
  @Override
183
  public VersionIdentifier resolveVersion(String tool, String edition, GenericVersionRange version, ToolCommandlet toolCommandlet) {
184

185
    MvnArtifact artifact = resolveArtifact(tool, edition, null, toolCommandlet);
7✔
186
    return resolveVersion(artifact, version);
5✔
187
  }
188

189
  /**
190
   * @param artifact the {@link MvnArtifact} to resolve. {@link MvnArtifact#getVersion() Version} should be undefined ("*").
191
   * @param version the {@link GenericVersionRange} to resolve.
192
   * @return the resolved {@link VersionIdentifier}.
193
   */
194
  public VersionIdentifier resolveVersion(MvnArtifact artifact, GenericVersionRange version) {
195

196
    artifact = artifact.withMavenMetadata();
3✔
197
    String versionString = version.toString();
3✔
198
    if (versionString.startsWith("*")) {
4!
199
      artifact = artifact.withVersion(versionString);
4✔
200
    }
201
    List<VersionIdentifier> versions = fetchVersions(artifact);
4✔
202
    VersionIdentifier resolvedVersion = VersionIdentifier.resolveVersionPattern(version, versions, this.context);
6✔
203
    versionString = resolvedVersion.toString();
3✔
204
    if (versionString.endsWith("-SNAPSHOT")) {
4✔
205
      artifact = artifact.withVersion(versionString);
4✔
206
      return resolveSnapshotVersion(getMavenUrl(artifact), versionString);
7✔
207
    }
208
    return resolvedVersion;
2✔
209
  }
210

211
  @Override
212
  protected List<VersionIdentifier> fetchVersions(MvnArtifact artifact) {
213

214
    String metadataUrl = getMavenUrl(artifact.withMavenMetadata());
5✔
215

216
    Document metadata = fetchXmlMetadata(metadataUrl);
4✔
217
    return fetchVersions(metadata, metadataUrl);
5✔
218
  }
219

220
  List<VersionIdentifier> fetchVersions(Document metadata, String source) {
221
    Element versioning = getFirstChildElement(metadata.getDocumentElement(), "versioning", source);
7✔
222
    Element versions = getFirstChildElement(versioning, "versions", source);
6✔
223
    NodeList versionsChildren = versions.getElementsByTagName("version");
4✔
224
    int length = versionsChildren.getLength();
3✔
225
    List<VersionIdentifier> versionList = new ArrayList<>(length);
5✔
226
    for (int i = length - 1; i >= 0; i--) {
8✔
227
      versionList.add(VersionIdentifier.of(versionsChildren.item(i).getTextContent()));
8✔
228
    }
229
    return versionList;
2✔
230
  }
231

232
  private VersionIdentifier resolveSnapshotVersion(String metadataUrl, String baseVersion) {
233
    Document metadata = fetchXmlMetadata(metadataUrl);
4✔
234
    return resolveSnapshotVersion(metadata, baseVersion, metadataUrl);
6✔
235
  }
236

237
  VersionIdentifier resolveSnapshotVersion(Document metadata, String baseVersion, String source) {
238
    Element versioning = getFirstChildElement(metadata.getDocumentElement(), "versioning", source);
7✔
239
    Element snapshot = getFirstChildElement(versioning, "snapshot", source);
6✔
240
    String timestamp = getFirstChildElement(snapshot, "timestamp", source).getTextContent();
7✔
241
    String buildNumber = getFirstChildElement(snapshot, "buildNumber", source).getTextContent();
7✔
242
    String version = baseVersion.replace("-SNAPSHOT", "-" + timestamp + "-" + buildNumber);
7✔
243
    return VersionIdentifier.of(version);
3✔
244
  }
245

246
  @Override
247
  public Path download(UrlDownloadFileMetadata metadata) {
248

249
    if (metadata instanceof MvnArtifactMetadata mvnMetadata) {
×
250
      return download(mvnMetadata);
×
251
    }
252
    return super.download(metadata);
×
253
  }
254

255
  /**
256
   * @param metadata the {@link MvnArtifactMetadata}.
257
   * @return the {@link Path} to the downloaded artifact.
258
   */
259
  public Path download(MvnArtifactMetadata metadata) {
260

261
    return getDownloadedArtifact(metadata.getMvnArtifact(), metadata.getChecksums());
7✔
262
  }
263

264
  private Element getFirstChildElement(Element element, String tag, Object source) {
265

266
    NodeList children = element.getChildNodes();
3✔
267
    int length = children.getLength();
3✔
268
    for (int i = 0; i < length; i++) {
7!
269
      Node node = children.item(i);
4✔
270
      if (node instanceof Element child) {
6✔
271
        if (child.getTagName().equals(tag)) {
5✔
272
          return child;
2✔
273
        }
274
      }
275
    }
276
    throw new IllegalStateException("Failed to resolve snapshot version - element " + tag + " not found in " + source);
×
277
  }
278

279
  private Document fetchXmlMetadata(String url) {
280

281
    try {
282
      return this.context.getNetworkStatus().invokeNetworkTask(() -> {
10✔
283
        URL xmlUrl = new URL(url);
5✔
284
        try (InputStream is = xmlUrl.openStream()) {
3✔
285
          return documentBuilder.parse(is);
7✔
286
        }
287
      }, url);
288
    } catch (Exception e) {
×
289
      throw new CliException("Failed to determine the latest version from " + url, e);
×
290
    }
291
  }
292

293
  /**
294
   * Used for tests to overwrite Maven base url.
295
   *
296
   * @param artifact the {@link MvnArtifact} to use
297
   * @return the Maven url
298
   */
299
  protected String getMavenUrl(MvnArtifact artifact) {
300
    return artifact.getDownloadUrl();
×
301
  }
302

303
  private class UrlLazyChecksums implements UrlChecksums {
304

305
    private final MvnArtifact artifact;
306

307
    private volatile List<UrlGenericChecksum> checksums;
308

309
    public UrlLazyChecksums(MvnArtifact artifact) {
3✔
310

311
      super();
2✔
312
      this.artifact = artifact;
3✔
313
    }
1✔
314

315
    @Override
316
    public Iterator<UrlGenericChecksum> iterator() {
317

318
      if (this.checksums == null) {
×
319
        synchronized (this) {
×
320
          if (this.checksums == null) {
×
321
            UrlGenericChecksum md5 = getChecksum(this.artifact, "MD5");
×
322
            UrlGenericChecksum sha1 = getChecksum(this.artifact, "SHA1");
×
323
            this.checksums = List.of(md5, sha1);
×
324
          }
325
        }
×
326
      }
327
      return this.checksums.iterator();
×
328
    }
329
  }
330
}
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