Refactor MavenJavadocService to improve XML parsing and error handling; introduce AppConfig for shared RestClient; enhance UpdateScheduler deletion logic.
Build and Push Docker Image / build-and-push (push) Successful in 1m12s

This commit is contained in:
2026-07-10 18:50:53 +02:00
parent 05c2735334
commit 77db44b1f5
5 changed files with 76 additions and 34 deletions
+14
View File
@@ -115,6 +115,20 @@
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<!-- build the fat jar -->
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<configuration> <configuration>
@@ -0,0 +1,14 @@
package codes.thischwa.jdvs.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
@Configuration
public class AppConfig {
@Bean
public RestClient restClient() {
return RestClient.create();
}
}
@@ -17,6 +17,10 @@ public class JdvsConfig {
private String mavenCentralUrl; private String mavenCentralUrl;
private List<RepoConfig> repositories; private List<RepoConfig> repositories;
public String getEffectiveMavenRepoUrl(RepoConfig cfg) {
return Objects.requireNonNullElse(cfg.getMavenRepoUrl(), mavenCentralUrl);
}
@Data @Data
public static class RepoConfig { public static class RepoConfig {
private String name; private String name;
@@ -2,6 +2,7 @@ package codes.thischwa.jdvs.service;
import codes.thischwa.jdvs.config.JdvsConfig; import codes.thischwa.jdvs.config.JdvsConfig;
import codes.thischwa.jdvs.config.JdvsConfig.RepoConfig; import codes.thischwa.jdvs.config.JdvsConfig.RepoConfig;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.nio.file.Files; import java.nio.file.Files;
@@ -11,13 +12,16 @@ import java.util.Optional;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient; import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientException;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -25,7 +29,7 @@ import org.xml.sax.InputSource;
public class MavenJavadocService { public class MavenJavadocService {
private final JdvsConfig jdvsConfig; private final JdvsConfig jdvsConfig;
private final RestClient restClient = RestClient.create(); private final RestClient restClient;
public Optional<String> fetchLatestVersion(String repoName) { public Optional<String> fetchLatestVersion(String repoName) {
RepoConfig cfg = findConfig(repoName); RepoConfig cfg = findConfig(repoName);
@@ -33,19 +37,16 @@ public class MavenJavadocService {
log.info("Fetching Maven metadata from {}", metadataUrl); log.info("Fetching Maven metadata from {}", metadataUrl);
try { try {
String xml = restClient.get().uri(metadataUrl).retrieve().body(String.class); String xml = restClient.get().uri(metadataUrl).retrieve().body(String.class);
Document doc = DocumentBuilderFactory.newInstance() Document doc = parseXml(xml);
.newDocumentBuilder() Optional<String> version = resolveVersionTag(doc);
.parse(new InputSource(new StringReader(xml))); if (version.isPresent()) {
String version = firstText(doc, "release"); log.info("Latest version of {} is {}", repoName, version.get());
if (version == null) { return version;
version = firstText(doc, "latest");
}
if (version != null) {
log.info("Latest version of {} is {}", repoName, version);
return Optional.of(version);
} }
log.warn("Could not determine latest version for {}", repoName); log.warn("Could not determine latest version for {}", repoName);
} catch (Exception e) { } catch (ParserConfigurationException | SAXException | IOException e) {
log.error("Failed to parse Maven metadata for {}", repoName, e);
} catch (RestClientException e) {
log.error("Failed to fetch Maven metadata for {}", repoName, e); log.error("Failed to fetch Maven metadata for {}", repoName, e);
} }
return Optional.empty(); return Optional.empty();
@@ -66,7 +67,7 @@ public class MavenJavadocService {
extractJavadocJar(jarUrl, outputDir); extractJavadocJar(jarUrl, outputDir);
log.info("Javadoc successfully extracted for {} to {}", repoName, outputDir); log.info("Javadoc successfully extracted for {} to {}", repoName, outputDir);
return true; return true;
} catch (Exception e) { } catch (IOException | RestClientException e) {
log.error("Failed to generate Javadoc for {} version {}", repoName, version, e); log.error("Failed to generate Javadoc for {} version {}", repoName, version, e);
return false; return false;
} }
@@ -78,7 +79,7 @@ public class MavenJavadocService {
throw new IOException("Empty response for " + jarUrl); throw new IOException("Empty response for " + jarUrl);
} }
Path safeOutputDir = outputDir.toAbsolutePath().normalize(); Path safeOutputDir = outputDir.toAbsolutePath().normalize();
try (ZipInputStream zip = new ZipInputStream(new java.io.ByteArrayInputStream(jarBytes))) { try (ZipInputStream zip = new ZipInputStream(new ByteArrayInputStream(jarBytes))) {
ZipEntry entry; ZipEntry entry;
while ((entry = zip.getNextEntry()) != null) { while ((entry = zip.getNextEntry()) != null) {
String name = entry.getName(); String name = entry.getName();
@@ -100,33 +101,31 @@ public class MavenJavadocService {
log.info("Resolving SNAPSHOT version from {}", metadataUrl); log.info("Resolving SNAPSHOT version from {}", metadataUrl);
try { try {
String xml = restClient.get().uri(metadataUrl).retrieve().body(String.class); String xml = restClient.get().uri(metadataUrl).retrieve().body(String.class);
Document doc = DocumentBuilderFactory.newInstance() Document doc = parseXml(xml);
.newDocumentBuilder()
.parse(new InputSource(new StringReader(xml)));
NodeList nodes = doc.getElementsByTagName("snapshotVersion"); NodeList nodes = doc.getElementsByTagName("snapshotVersion");
for (int i = 0; i < nodes.getLength(); i++) { for (int i = 0; i < nodes.getLength(); i++) {
org.w3c.dom.Element el = (org.w3c.dom.Element) nodes.item(i); org.w3c.dom.Element el = (org.w3c.dom.Element) nodes.item(i);
String classifier = firstText(el, "classifier"); String classifier = firstText(el.getElementsByTagName("classifier"));
String ext = firstText(el, "extension"); String ext = firstText(el.getElementsByTagName("extension"));
String value = firstText(el, "value"); String value = firstText(el.getElementsByTagName("value"));
if ("javadoc".equals(classifier) && "jar".equals(ext) && value != null) { if ("javadoc".equals(classifier) && "jar".equals(ext) && value != null) {
log.info("Resolved SNAPSHOT version to {}", value); log.info("Resolved SNAPSHOT version to {}", value);
return value; return value;
} }
} }
} catch (Exception e) { } catch (ParserConfigurationException | SAXException | IOException e) {
log.warn("Could not resolve SNAPSHOT version for {}, falling back to {}", log.warn("Could not parse SNAPSHOT metadata for {}, falling back to {}",
cfg.getArtifactId(), version, e);
} catch (RestClientException e) {
log.warn("Could not fetch SNAPSHOT metadata for {}, falling back to {}",
cfg.getArtifactId(), version, e); cfg.getArtifactId(), version, e);
} }
return version; return version;
} }
private String baseUrl(RepoConfig cfg) { private String baseUrl(RepoConfig cfg) {
String repoUrl = cfg.getMavenRepoUrl() != null
? cfg.getMavenRepoUrl()
: jdvsConfig.getMavenCentralUrl();
String groupPath = cfg.getGroupId().replace('.', '/'); String groupPath = cfg.getGroupId().replace('.', '/');
return repoUrl + "/" + groupPath + "/" + cfg.getArtifactId(); return jdvsConfig.getEffectiveMavenRepoUrl(cfg) + "/" + groupPath + "/" + cfg.getArtifactId();
} }
private RepoConfig findConfig(String repoName) { private RepoConfig findConfig(String repoName) {
@@ -136,13 +135,19 @@ public class MavenJavadocService {
.orElseThrow(() -> new IllegalArgumentException("No config found for: " + repoName)); .orElseThrow(() -> new IllegalArgumentException("No config found for: " + repoName));
} }
private String firstText(Document doc, String tagName) { private static Optional<String> resolveVersionTag(Document doc) {
NodeList nodes = doc.getElementsByTagName(tagName); return Optional.ofNullable(firstText(doc.getElementsByTagName("release")))
.or(() -> Optional.ofNullable(firstText(doc.getElementsByTagName("latest"))));
}
private static String firstText(NodeList nodes) {
return nodes.getLength() > 0 ? nodes.item(0).getTextContent() : null; return nodes.getLength() > 0 ? nodes.item(0).getTextContent() : null;
} }
private String firstText(org.w3c.dom.Element parent, String tagName) { private static Document parseXml(String xml)
NodeList nodes = parent.getElementsByTagName(tagName); throws ParserConfigurationException, SAXException, IOException {
return nodes.getLength() > 0 ? nodes.item(0).getTextContent() : null; return DocumentBuilderFactory.newInstance()
.newDocumentBuilder()
.parse(new InputSource(new StringReader(xml)));
} }
} }
@@ -3,7 +3,6 @@ package codes.thischwa.jdvs.service;
import codes.thischwa.jdvs.config.JdvsConfig; import codes.thischwa.jdvs.config.JdvsConfig;
import codes.thischwa.jdvs.jpa.GitRepositoryRepository; import codes.thischwa.jdvs.jpa.GitRepositoryRepository;
import codes.thischwa.jdvs.model.GitRepository; import codes.thischwa.jdvs.model.GitRepository;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -47,10 +46,16 @@ public class UpdateScheduler {
return; return;
} }
try (var stream = Files.walk(baseDir)) { try (var stream = Files.walk(baseDir)) {
stream.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); stream.sorted(Comparator.reverseOrder()).forEach(path -> {
try {
Files.delete(path);
} catch (IOException e) {
log.warn("Failed to delete path: {}", path, e);
}
});
log.info("Deleted base directory: {}", baseDir); log.info("Deleted base directory: {}", baseDir);
} catch (IOException e) { } catch (IOException e) {
log.error("Failed to delete base directory: {}", baseDir, e); log.error("Failed to walk base directory: {}", baseDir, e);
} }
} }