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

devonfw / IDEasy / 18880966434

28 Oct 2025 03:55PM UTC coverage: 68.862% (+0.3%) from 68.544%
18880966434

Pull #1496

github

web-flow
Merge 5e72e0006 into 8749d60f0
Pull Request #1496: #1492: Add Spring-Boot-CLI Commandlet

3484 of 5543 branches covered (62.85%)

Branch coverage included in aggregate %.

9115 of 12753 relevant lines covered (71.47%)

3.14 hits per line

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

67.27
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.context.IdeContext;
22
import com.devonfw.tools.ide.os.OperatingSystem;
23
import com.devonfw.tools.ide.os.SystemArchitecture;
24
import com.devonfw.tools.ide.tool.IdeasyCommandlet;
25
import com.devonfw.tools.ide.tool.ToolCommandlet;
26
import com.devonfw.tools.ide.tool.mvn.MvnArtifact;
27
import com.devonfw.tools.ide.tool.mvn.MvnBasedLocalToolCommandlet;
28
import com.devonfw.tools.ide.url.model.file.UrlChecksums;
29
import com.devonfw.tools.ide.url.model.file.UrlDownloadFileMetadata;
30
import com.devonfw.tools.ide.url.model.file.UrlGenericChecksum;
31
import com.devonfw.tools.ide.url.model.file.UrlGenericChecksumType;
32
import com.devonfw.tools.ide.variable.IdeVariables;
33
import com.devonfw.tools.ide.version.GenericVersionRange;
34
import com.devonfw.tools.ide.version.VersionIdentifier;
35

36
/**
37
 * Implementation of {@link AbstractToolRepository} for {@link com.devonfw.tools.ide.tool.java.Java java}-based artifacts. Actually
38
 * {@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
39
 * {@link com.devonfw.tools.ide.tool.gradle.Gradle}. However, it is still called maven-repository and not java-repository.
40
 */
41
public class MvnRepository extends ArtifactToolRepository<MvnArtifact, MvnArtifactMetadata> {
42

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

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

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

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

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

56
  private final Path localMavenRepository;
57

58
  private final DocumentBuilder documentBuilder;
59

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

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

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

82
  @Override
83
  public String getId() {
84

85
    return ID;
2✔
86
  }
87

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

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

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

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

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

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

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

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

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

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

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

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

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

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

213
    String metadataUrl = artifact.withMavenMetadata().getDownloadUrl();
4✔
214
    Document metadata = fetchXmlMetadata(metadataUrl);
4✔
215
    return fetchVersions(metadata, metadataUrl);
5✔
216
  }
217

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

230
  private VersionIdentifier resolveSnapshotVersion(String metadataUrl, String baseVersion) {
231
    Document metadata = fetchXmlMetadata(metadataUrl);
×
232
    return resolveSnapshotVersion(metadata, baseVersion, metadataUrl);
×
233
  }
234

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

244
  @Override
245
  public Path download(UrlDownloadFileMetadata metadata) {
246

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

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

259
    return getDownloadedArtifact(metadata.getMvnArtifact(), metadata.getChecksums());
7✔
260
  }
261

262
  private Element getFirstChildElement(Element element, String tag, Object source) {
263

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

277
  private Document fetchXmlMetadata(String url) {
278

279
    try {
280
      URL xmlUrl = new URL(url);
5✔
281
      try (InputStream is = xmlUrl.openStream()) {
3✔
282
        return documentBuilder.parse(is);
7✔
283
      }
284
    } catch (Exception e) {
×
285
      throw new IllegalStateException("Failed to fetch XML metadata from " + url, e);
×
286
    }
287
  }
288

289
  private class UrlLazyChecksums implements UrlChecksums {
290

291
    private final MvnArtifact artifact;
292

293
    private volatile List<UrlGenericChecksum> checksums;
294

295
    public UrlLazyChecksums(MvnArtifact artifact) {
3✔
296

297
      super();
2✔
298
      this.artifact = artifact;
3✔
299
    }
1✔
300

301
    @Override
302
    public Iterator<UrlGenericChecksum> iterator() {
303

304
      if (this.checksums == null) {
×
305
        synchronized (this) {
×
306
          if (this.checksums == null) {
×
307
            UrlGenericChecksum md5 = getChecksum(this.artifact, "MD5");
×
308
            UrlGenericChecksum sha1 = getChecksum(this.artifact, "SHA1");
×
309
            this.checksums = List.of(md5, sha1);
×
310
          }
311
        }
×
312
      }
313
      return this.checksums.iterator();
×
314
    }
315
  }
316
}
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

© 2025 Coveralls, Inc