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

devonfw / IDEasy / 13440922988

20 Feb 2025 05:16PM UTC coverage: 67.945% (-0.01%) from 67.959%
13440922988

push

github

web-flow
#1053: fix setup #1044: setup rewritten in Java (#1055)

Co-authored-by: jan-vcapgemini <59438728+jan-vcapgemini@users.noreply.github.com>

2992 of 4849 branches covered (61.7%)

Branch coverage included in aggregate %.

7765 of 10983 relevant lines covered (70.7%)

3.08 hits per line

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

44.31
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.Collection;
10
import java.util.Collections;
11
import java.util.Comparator;
12
import java.util.Iterator;
13
import java.util.List;
14
import java.util.Locale;
15
import java.util.Map;
16
import javax.xml.parsers.DocumentBuilder;
17
import javax.xml.parsers.DocumentBuilderFactory;
18

19
import org.w3c.dom.Document;
20
import org.w3c.dom.Element;
21
import org.w3c.dom.Node;
22
import org.w3c.dom.NodeList;
23

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

40
/**
41
 * Implementation of {@link AbstractToolRepository} for maven-based artifacts.
42
 */
43
public final class MavenRepository extends AbstractToolRepository {
44

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

48
  /** Base URL for Maven Snapshots repository */
49
  public static final String MAVEN_SNAPSHOTS = "https://s01.oss.sonatype.org/content/repositories/snapshots";
50

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

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

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

58
  private final Path localMavenRepository;
59

60
  private final DocumentBuilder documentBuilder;
61

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

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

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

84
  @Override
85
  public String getId() {
86

87
    return ID;
×
88
  }
89

90
  private MvnArtifact resolveArtifact(String tool, String edition, ToolCommandlet toolCommandlet) {
91
    MvnArtifact artifact;
92
    if (toolCommandlet instanceof MvnBasedLocalToolCommandlet mvnBasedTool) {
3!
93
      artifact = mvnBasedTool.getArtifact(edition);
×
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
    return artifact;
2✔
105
  }
106

107
  /**
108
   * @param artifact the {@link MvnArtifact} to resolve.
109
   * @param tool the {@link MavenArtifactMetadata#getTool() tool name}.
110
   * @param edition the {@link MavenArtifactMetadata#getEdition() tool edition}.
111
   * @param version the {@link MavenArtifactMetadata#getVersion() tool version}.
112
   * @return the resolved {@link MavenArtifactMetadata}.
113
   */
114
  public MavenArtifactMetadata resolveArtifact(MvnArtifact artifact, String tool, String edition, VersionIdentifier version) {
115
    OperatingSystem os = null;
2✔
116
    SystemArchitecture arch = null;
2✔
117
    String classifier = artifact.getClassifier();
3✔
118
    if (!classifier.isEmpty()) {
3!
119
      String resolvedClassifier;
120
      os = this.context.getSystemInfo().getOs();
5✔
121
      resolvedClassifier = classifier.replace("${os}", os.toString());
6✔
122
      if (resolvedClassifier.equals(classifier)) {
4!
123
        os = null;
×
124
      } else {
125
        classifier = resolvedClassifier;
2✔
126
      }
127
      arch = this.context.getSystemInfo().getArchitecture();
5✔
128
      resolvedClassifier = classifier.replace("${arch}", arch.toString());
6✔
129
      if (resolvedClassifier.equals(classifier)) {
4!
130
        arch = null;
×
131
      }
132
      artifact = artifact.withClassifier(resolvedClassifier);
4✔
133
    }
134
    UrlChecksums chekcsums = null;
2✔
135
    if (version != null) {
2!
136
      artifact = artifact.withVersion(version.toString());
5✔
137
      chekcsums = new UrlLazyChecksums(artifact);
6✔
138
    }
139
    return new MavenArtifactMetadata(artifact, tool, edition, chekcsums, os, arch);
10✔
140
  }
141

142
  private UrlGenericChecksum getChecksum(MvnArtifact artifact, String hashAlgorithm) {
143

144
    MvnArtifact checksumArtifact = artifact.withType(artifact.getType() + "." + hashAlgorithm.toLowerCase(Locale.ROOT));
×
145
    Path checksumFile = getDownloadedArtifact(checksumArtifact, null);
×
146
    String checksum = this.context.getFileAccess().readFileContent(checksumFile).trim();
×
147
    return new UrlGenericChecksumType(checksum, hashAlgorithm, checksumFile);
×
148
  }
149

150
  private Path getDownloadedArtifact(MvnArtifact artifact, UrlChecksums checksums) {
151

152
    Path file = this.localMavenRepository.resolve(artifact.getPath());
×
153
    if (isNotUpToDateInLocalRepo(file)) {
×
154
      this.context.getFileAccess().mkdirs(file.getParent());
×
155
      download(artifact.getDownloadUrl(), file, artifact.getVersion(), checksums);
×
156
    }
157
    return file;
×
158
  }
159

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

174
  @Override
175
  protected UrlDownloadFileMetadata getMetadata(String tool, String edition, VersionIdentifier version, ToolCommandlet toolCommandlet) {
176

177
    MvnArtifact artifact = resolveArtifact(tool, edition, toolCommandlet);
6✔
178
    return resolveArtifact(artifact, tool, edition, version);
7✔
179
  }
180

181

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

185
    MvnArtifact artifact = resolveArtifact(tool, edition, toolCommandlet);
×
186
    return resolveVersion(artifact, version);
×
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();
×
197
    String versionString = version.toString();
×
198
    if (versionString.startsWith("*")) {
×
199
      artifact = artifact.withVersion(versionString);
×
200
    }
201
    List<VersionIdentifier> versions = fetchVersions(artifact);
×
202
    VersionIdentifier resolvedVersion = VersionIdentifier.resolveVersionPattern(version, versions, this.context);
×
203
    versionString = resolvedVersion.toString();
×
204
    if (versionString.endsWith("-SNAPSHOT")) {
×
205
      artifact = artifact.withVersion(versionString);
×
206
      return resolveSnapshotVersion(artifact.getDownloadUrl(), versionString);
×
207
    }
208
    return resolvedVersion;
×
209
  }
210

211
  private List<VersionIdentifier> fetchVersions(MvnArtifact artifact) {
212

213
    String metadataUrl = artifact.withMavenMetadata().getDownloadUrl();
×
214
    Document metadata = fetchXmlMetadata(metadataUrl);
×
215
    return fetchVersions(metadata, metadataUrl);
×
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 = 0; i < length; i++) {
7✔
225
      versionList.add(VersionIdentifier.of(versionsChildren.item(i).getTextContent()));
8✔
226
    }
227
    versionList.sort(Comparator.reverseOrder());
3✔
228
    return versionList;
2✔
229
  }
230

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

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

245
  @Override
246
  public List<String> getSortedEditions(String tool) {
247

248
    return List.of(tool);
×
249
  }
250

251
  @Override
252
  public List<VersionIdentifier> getSortedVersions(String tool, String edition, ToolCommandlet toolCommandlet) {
253

254
    MvnArtifact artifact = resolveArtifact(tool, edition, toolCommandlet);
×
255
    return fetchVersions(artifact);
×
256
  }
257

258
  @Override
259
  public Collection<ToolDependency> findDependencies(String groupId, String artifactId, VersionIdentifier version) {
260

261
    // We could read POM here and find dependencies but we do not want to reimplement maven here.
262
    // For our use-case we only download bundled packages from maven central so we do KISS for now.
263
    return Collections.emptyList();
×
264
  }
265

266
  @Override
267
  public Path download(UrlDownloadFileMetadata metadata) {
268

269
    if (metadata instanceof MavenArtifactMetadata mvnMetadata) {
×
270
      return download(mvnMetadata);
×
271
    }
272
    return super.download(metadata);
×
273
  }
274

275
  /**
276
   * @param metadata the {@link MavenArtifactMetadata}.
277
   * @return the {@link Path} to the downloaded artifact.
278
   */
279
  public Path download(MavenArtifactMetadata metadata) {
280

281
    return getDownloadedArtifact(metadata.getMvnArtifact(), metadata.getChecksums());
×
282
  }
283

284
  private Element getFirstChildElement(Element element, String tag, Object source) {
285

286
    NodeList children = element.getChildNodes();
3✔
287
    int length = children.getLength();
3✔
288
    for (int i = 0; i < length; i++) {
7!
289
      Node node = children.item(i);
4✔
290
      if (node instanceof Element child) {
6✔
291
        if (child.getTagName().equals(tag)) {
5✔
292
          return child;
2✔
293
        }
294
      }
295
    }
296
    throw new IllegalStateException("Failed to resolve snapshot version - element " + tag + " not found in " + source);
×
297
  }
298

299
  private Document fetchXmlMetadata(String url) {
300

301
    try {
302
      URL xmlUrl = new URL(url);
×
303
      try (InputStream is = xmlUrl.openStream()) {
×
304
        return documentBuilder.parse(is);
×
305
      }
306
    } catch (Exception e) {
×
307
      throw new IllegalStateException("Failed to fetch XML metadata from " + url, e);
×
308
    }
309
  }
310

311
  private class UrlLazyChecksums implements UrlChecksums {
312

313
    private final MvnArtifact artifact;
314

315
    private volatile List<UrlGenericChecksum> checksums;
316

317
    public UrlLazyChecksums(MvnArtifact artifact) {
3✔
318

319
      super();
2✔
320
      this.artifact = artifact;
3✔
321
    }
1✔
322

323
    @Override
324
    public Iterator<UrlGenericChecksum> iterator() {
325

326
      if (this.checksums == null) {
×
327
        synchronized (this) {
×
328
          if (this.checksums == null) {
×
329
            UrlGenericChecksum md5 = getChecksum(this.artifact, "MD5");
×
330
            UrlGenericChecksum sha1 = getChecksum(this.artifact, "SHA1");
×
331
            this.checksums = List.of(md5, sha1);
×
332
          }
333
        }
×
334
      }
335
      return this.checksums.iterator();
×
336
    }
337
  }
338

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