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

devonfw / IDEasy / 14172089567

31 Mar 2025 12:57PM UTC coverage: 67.764% (+0.3%) from 67.422%
14172089567

Pull #1183

github

web-flow
Merge e709d33f3 into a43982a80
Pull Request #1183: #103: create java resemblance for security.json

3053 of 4934 branches covered (61.88%)

Branch coverage included in aggregate %.

7880 of 11200 relevant lines covered (70.36%)

3.07 hits per line

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

44.05
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.url.model.file.json.ToolSecurity;
37
import com.devonfw.tools.ide.variable.IdeVariables;
38
import com.devonfw.tools.ide.version.GenericVersionRange;
39
import com.devonfw.tools.ide.version.VersionIdentifier;
40

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

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

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

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

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

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

59
  private final Path localMavenRepository;
60

61
  private final DocumentBuilder documentBuilder;
62

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

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

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

85
  @Override
86
  public String getId() {
87

88
    return ID;
×
89
  }
90

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

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

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

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

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

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

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

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

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

182

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

186
    MvnArtifact artifact = resolveArtifact(tool, edition, toolCommandlet);
×
187
    return resolveVersion(artifact, version);
×
188
  }
189

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

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

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

214
    String metadataUrl = artifact.withMavenMetadata().getDownloadUrl();
×
215
    Document metadata = fetchXmlMetadata(metadataUrl);
×
216
    return fetchVersions(metadata, metadataUrl);
×
217
  }
218

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

232
  private VersionIdentifier resolveSnapshotVersion(String metadataUrl, String baseVersion) {
233
    Document metadata = fetchXmlMetadata(metadataUrl);
×
234
    return resolveSnapshotVersion(metadata, baseVersion, metadataUrl);
×
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 List<String> getSortedEditions(String tool) {
248

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

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

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

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

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

267
  @Override
268
  public ToolSecurity findSecurity(String tool, String edition) {
269
    return ToolSecurity.getEmpty();
×
270
  }
271

272
  @Override
273
  public Path download(UrlDownloadFileMetadata metadata) {
274

275
    if (metadata instanceof MavenArtifactMetadata mvnMetadata) {
×
276
      return download(mvnMetadata);
×
277
    }
278
    return super.download(metadata);
×
279
  }
280

281
  /**
282
   * @param metadata the {@link MavenArtifactMetadata}.
283
   * @return the {@link Path} to the downloaded artifact.
284
   */
285
  public Path download(MavenArtifactMetadata metadata) {
286

287
    return getDownloadedArtifact(metadata.getMvnArtifact(), metadata.getChecksums());
×
288
  }
289

290
  private Element getFirstChildElement(Element element, String tag, Object source) {
291

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

305
  private Document fetchXmlMetadata(String url) {
306

307
    try {
308
      URL xmlUrl = new URL(url);
×
309
      try (InputStream is = xmlUrl.openStream()) {
×
310
        return documentBuilder.parse(is);
×
311
      }
312
    } catch (Exception e) {
×
313
      throw new IllegalStateException("Failed to fetch XML metadata from " + url, e);
×
314
    }
315
  }
316

317
  private class UrlLazyChecksums implements UrlChecksums {
318

319
    private final MvnArtifact artifact;
320

321
    private volatile List<UrlGenericChecksum> checksums;
322

323
    public UrlLazyChecksums(MvnArtifact artifact) {
3✔
324

325
      super();
2✔
326
      this.artifact = artifact;
3✔
327
    }
1✔
328

329
    @Override
330
    public Iterator<UrlGenericChecksum> iterator() {
331

332
      if (this.checksums == null) {
×
333
        synchronized (this) {
×
334
          if (this.checksums == null) {
×
335
            UrlGenericChecksum md5 = getChecksum(this.artifact, "MD5");
×
336
            UrlGenericChecksum sha1 = getChecksum(this.artifact, "SHA1");
×
337
            this.checksums = List.of(md5, sha1);
×
338
          }
339
        }
×
340
      }
341
      return this.checksums.iterator();
×
342
    }
343
  }
344

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