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

devonfw / IDEasy / 17832256085

18 Sep 2025 02:38PM UTC coverage: 68.464% (-0.08%) from 68.539%
17832256085

Pull #1499

github

web-flow
Merge 0ad75342a into 147222dbe
Pull Request #1499: #907: add NpmRepository and further node/npm support as preparation for yarn and corepack

3430 of 5487 branches covered (62.51%)

Branch coverage included in aggregate %.

8977 of 12635 relevant lines covered (71.05%)

3.12 hits per line

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

44.79
cli/src/main/java/com/devonfw/tools/ide/tool/repository/MavenRepository.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 final class MavenRepository extends ArtifactToolRepository<MvnArtifact, MavenArtifactMetadata> {
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 MavenRepository(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;
×
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) {
3!
92
      artifact = mvnBasedTool.getArtifact(edition);
×
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 MavenArtifactMetadata 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;
×
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;
×
127
      }
128
      artifact = artifact.withClassifier(resolvedClassifier);
4✔
129
    }
130
    UrlChecksums chekcsums = null;
2✔
131
    if (!artifact.isMavenMetadata()) {
3!
132
      chekcsums = new UrlLazyChecksums(artifact);
6✔
133
    }
134
    return new MavenArtifactMetadata(artifact, tool, edition, chekcsums, os, arch);
10✔
135
  }
136

137
  private UrlGenericChecksum getChecksum(MvnArtifact artifact, String hashAlgorithm) {
138

139
    MvnArtifact checksumArtifact = artifact.withType(artifact.getType() + "." + hashAlgorithm.toLowerCase(Locale.ROOT));
×
140
    Path checksumFile = getDownloadedArtifact(checksumArtifact, null);
×
141
    String checksum = this.context.getFileAccess().readFileContent(checksumFile).trim();
×
142
    return new UrlGenericChecksumType(checksum, hashAlgorithm, checksumFile);
×
143
  }
144

145
  private Path getDownloadedArtifact(MvnArtifact artifact, UrlChecksums checksums) {
146

147
    Path file = this.localMavenRepository.resolve(artifact.getPath());
×
148
    if (isNotUpToDateInLocalRepo(file)) {
×
149
      this.context.getFileAccess().mkdirs(file.getParent());
×
150
      download(artifact.getDownloadUrl(), file, artifact.getVersion(), checksums);
×
151
    }
152
    return file;
×
153
  }
154

155
  private boolean isNotUpToDateInLocalRepo(Path file) {
156
    if (!Files.exists(file)) {
×
157
      return true;
×
158
    }
159
    if (file.getFileName().toString().equals(MvnArtifact.MAVEN_METADATA_XML)) {
×
160
      Duration cacheDuration = METADATA_CACHE_DURATION_RELEASE;
×
161
      if (file.getParent().getFileName().toString().endsWith("-SNAPSHOT")) {
×
162
        cacheDuration = METADATA_CACHE_DURATION_SNAPSHOT;
×
163
      }
164
      return !this.context.getFileAccess().isFileAgeRecent(file, cacheDuration);
×
165
    }
166
    return false;
×
167
  }
168

169
  @Override
170
  public VersionIdentifier resolveVersion(String tool, String edition, GenericVersionRange version, ToolCommandlet toolCommandlet) {
171

172
    MvnArtifact artifact = resolveArtifact(tool, edition, null, toolCommandlet);
×
173
    return resolveVersion(artifact, version);
×
174
  }
175

176
  /**
177
   * @param artifact the {@link MvnArtifact} to resolve. {@link MvnArtifact#getVersion() Version} should be undefined ("*").
178
   * @param version the {@link GenericVersionRange} to resolve.
179
   * @return the resolved {@link VersionIdentifier}.
180
   */
181
  public VersionIdentifier resolveVersion(MvnArtifact artifact, GenericVersionRange version) {
182

183
    artifact = artifact.withMavenMetadata();
×
184
    String versionString = version.toString();
×
185
    if (versionString.startsWith("*")) {
×
186
      artifact = artifact.withVersion(versionString);
×
187
    }
188
    List<VersionIdentifier> versions = fetchVersions(artifact);
×
189
    VersionIdentifier resolvedVersion = VersionIdentifier.resolveVersionPattern(version, versions, this.context);
×
190
    versionString = resolvedVersion.toString();
×
191
    if (versionString.endsWith("-SNAPSHOT")) {
×
192
      artifact = artifact.withVersion(versionString);
×
193
      return resolveSnapshotVersion(artifact.getDownloadUrl(), versionString);
×
194
    }
195
    return resolvedVersion;
×
196
  }
197

198
  @Override
199
  protected List<VersionIdentifier> fetchVersions(MvnArtifact artifact) {
200

201
    String metadataUrl = artifact.withMavenMetadata().getDownloadUrl();
×
202
    Document metadata = fetchXmlMetadata(metadataUrl);
×
203
    return fetchVersions(metadata, metadataUrl);
×
204
  }
205

206
  List<VersionIdentifier> fetchVersions(Document metadata, String source) {
207
    Element versioning = getFirstChildElement(metadata.getDocumentElement(), "versioning", source);
7✔
208
    Element versions = getFirstChildElement(versioning, "versions", source);
6✔
209
    NodeList versionsChildren = versions.getElementsByTagName("version");
4✔
210
    int length = versionsChildren.getLength();
3✔
211
    List<VersionIdentifier> versionList = new ArrayList<>(length);
5✔
212
    for (int i = 0; i < length; i++) {
7✔
213
      versionList.add(VersionIdentifier.of(versionsChildren.item(i).getTextContent()));
8✔
214
    }
215
    return versionList;
2✔
216
  }
217

218
  private VersionIdentifier resolveSnapshotVersion(String metadataUrl, String baseVersion) {
219
    Document metadata = fetchXmlMetadata(metadataUrl);
×
220
    return resolveSnapshotVersion(metadata, baseVersion, metadataUrl);
×
221
  }
222

223
  VersionIdentifier resolveSnapshotVersion(Document metadata, String baseVersion, String source) {
224
    Element versioning = getFirstChildElement(metadata.getDocumentElement(), "versioning", source);
7✔
225
    Element snapshot = getFirstChildElement(versioning, "snapshot", source);
6✔
226
    String timestamp = getFirstChildElement(snapshot, "timestamp", source).getTextContent();
7✔
227
    String buildNumber = getFirstChildElement(snapshot, "buildNumber", source).getTextContent();
7✔
228
    String version = baseVersion.replace("-SNAPSHOT", "-" + timestamp + "-" + buildNumber);
7✔
229
    return VersionIdentifier.of(version);
3✔
230
  }
231

232
  @Override
233
  public Path download(UrlDownloadFileMetadata metadata) {
234

235
    if (metadata instanceof MavenArtifactMetadata mvnMetadata) {
×
236
      return download(mvnMetadata);
×
237
    }
238
    return super.download(metadata);
×
239
  }
240

241
  /**
242
   * @param metadata the {@link MavenArtifactMetadata}.
243
   * @return the {@link Path} to the downloaded artifact.
244
   */
245
  public Path download(MavenArtifactMetadata metadata) {
246

247
    return getDownloadedArtifact(metadata.getMvnArtifact(), metadata.getChecksums());
×
248
  }
249

250
  private Element getFirstChildElement(Element element, String tag, Object source) {
251

252
    NodeList children = element.getChildNodes();
3✔
253
    int length = children.getLength();
3✔
254
    for (int i = 0; i < length; i++) {
7!
255
      Node node = children.item(i);
4✔
256
      if (node instanceof Element child) {
6✔
257
        if (child.getTagName().equals(tag)) {
5✔
258
          return child;
2✔
259
        }
260
      }
261
    }
262
    throw new IllegalStateException("Failed to resolve snapshot version - element " + tag + " not found in " + source);
×
263
  }
264

265
  private Document fetchXmlMetadata(String url) {
266

267
    try {
268
      URL xmlUrl = new URL(url);
×
269
      try (InputStream is = xmlUrl.openStream()) {
×
270
        return documentBuilder.parse(is);
×
271
      }
272
    } catch (Exception e) {
×
273
      throw new IllegalStateException("Failed to fetch XML metadata from " + url, e);
×
274
    }
275
  }
276

277
  private class UrlLazyChecksums implements UrlChecksums {
278

279
    private final MvnArtifact artifact;
280

281
    private volatile List<UrlGenericChecksum> checksums;
282

283
    public UrlLazyChecksums(MvnArtifact artifact) {
3✔
284

285
      super();
2✔
286
      this.artifact = artifact;
3✔
287
    }
1✔
288

289
    @Override
290
    public Iterator<UrlGenericChecksum> iterator() {
291

292
      if (this.checksums == null) {
×
293
        synchronized (this) {
×
294
          if (this.checksums == null) {
×
295
            UrlGenericChecksum md5 = getChecksum(this.artifact, "MD5");
×
296
            UrlGenericChecksum sha1 = getChecksum(this.artifact, "SHA1");
×
297
            this.checksums = List.of(md5, sha1);
×
298
          }
299
        }
×
300
      }
301
      return this.checksums.iterator();
×
302
    }
303
  }
304

305
}
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