diff --git a/pom.xml b/pom.xml index e5face6..d701d45 100644 --- a/pom.xml +++ b/pom.xml @@ -115,6 +115,20 @@ + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar-no-fork + + + + + + + org.springframework.boot spring-boot-maven-plugin diff --git a/src/main/java/codes/thischwa/jdvs/config/AppConfig.java b/src/main/java/codes/thischwa/jdvs/config/AppConfig.java new file mode 100644 index 0000000..e68e4fc --- /dev/null +++ b/src/main/java/codes/thischwa/jdvs/config/AppConfig.java @@ -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(); + } +} diff --git a/src/main/java/codes/thischwa/jdvs/config/JdvsConfig.java b/src/main/java/codes/thischwa/jdvs/config/JdvsConfig.java index 295ce1e..63bf380 100644 --- a/src/main/java/codes/thischwa/jdvs/config/JdvsConfig.java +++ b/src/main/java/codes/thischwa/jdvs/config/JdvsConfig.java @@ -17,6 +17,10 @@ public class JdvsConfig { private String mavenCentralUrl; private List repositories; + public String getEffectiveMavenRepoUrl(RepoConfig cfg) { + return Objects.requireNonNullElse(cfg.getMavenRepoUrl(), mavenCentralUrl); + } + @Data public static class RepoConfig { private String name; diff --git a/src/main/java/codes/thischwa/jdvs/service/MavenJavadocService.java b/src/main/java/codes/thischwa/jdvs/service/MavenJavadocService.java index bc87ab4..1ac0d58 100644 --- a/src/main/java/codes/thischwa/jdvs/service/MavenJavadocService.java +++ b/src/main/java/codes/thischwa/jdvs/service/MavenJavadocService.java @@ -2,6 +2,7 @@ package codes.thischwa.jdvs.service; import codes.thischwa.jdvs.config.JdvsConfig; import codes.thischwa.jdvs.config.JdvsConfig.RepoConfig; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringReader; import java.nio.file.Files; @@ -11,13 +12,16 @@ import java.util.Optional; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClientException; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; +import org.xml.sax.SAXException; @Service @RequiredArgsConstructor @@ -25,7 +29,7 @@ import org.xml.sax.InputSource; public class MavenJavadocService { private final JdvsConfig jdvsConfig; - private final RestClient restClient = RestClient.create(); + private final RestClient restClient; public Optional fetchLatestVersion(String repoName) { RepoConfig cfg = findConfig(repoName); @@ -33,19 +37,16 @@ public class MavenJavadocService { log.info("Fetching Maven metadata from {}", metadataUrl); try { String xml = restClient.get().uri(metadataUrl).retrieve().body(String.class); - Document doc = DocumentBuilderFactory.newInstance() - .newDocumentBuilder() - .parse(new InputSource(new StringReader(xml))); - String version = firstText(doc, "release"); - if (version == null) { - version = firstText(doc, "latest"); - } - if (version != null) { - log.info("Latest version of {} is {}", repoName, version); - return Optional.of(version); + Document doc = parseXml(xml); + Optional version = resolveVersionTag(doc); + if (version.isPresent()) { + log.info("Latest version of {} is {}", repoName, version.get()); + return version; } 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); } return Optional.empty(); @@ -66,7 +67,7 @@ public class MavenJavadocService { extractJavadocJar(jarUrl, outputDir); log.info("Javadoc successfully extracted for {} to {}", repoName, outputDir); return true; - } catch (Exception e) { + } catch (IOException | RestClientException e) { log.error("Failed to generate Javadoc for {} version {}", repoName, version, e); return false; } @@ -78,7 +79,7 @@ public class MavenJavadocService { throw new IOException("Empty response for " + jarUrl); } 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; while ((entry = zip.getNextEntry()) != null) { String name = entry.getName(); @@ -100,33 +101,31 @@ public class MavenJavadocService { log.info("Resolving SNAPSHOT version from {}", metadataUrl); try { String xml = restClient.get().uri(metadataUrl).retrieve().body(String.class); - Document doc = DocumentBuilderFactory.newInstance() - .newDocumentBuilder() - .parse(new InputSource(new StringReader(xml))); + Document doc = parseXml(xml); NodeList nodes = doc.getElementsByTagName("snapshotVersion"); for (int i = 0; i < nodes.getLength(); i++) { org.w3c.dom.Element el = (org.w3c.dom.Element) nodes.item(i); - String classifier = firstText(el, "classifier"); - String ext = firstText(el, "extension"); - String value = firstText(el, "value"); + String classifier = firstText(el.getElementsByTagName("classifier")); + String ext = firstText(el.getElementsByTagName("extension")); + String value = firstText(el.getElementsByTagName("value")); if ("javadoc".equals(classifier) && "jar".equals(ext) && value != null) { log.info("Resolved SNAPSHOT version to {}", value); return value; } } - } catch (Exception e) { - log.warn("Could not resolve SNAPSHOT version for {}, falling back to {}", + } catch (ParserConfigurationException | SAXException | IOException e) { + 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); } return version; } private String baseUrl(RepoConfig cfg) { - String repoUrl = cfg.getMavenRepoUrl() != null - ? cfg.getMavenRepoUrl() - : jdvsConfig.getMavenCentralUrl(); String groupPath = cfg.getGroupId().replace('.', '/'); - return repoUrl + "/" + groupPath + "/" + cfg.getArtifactId(); + return jdvsConfig.getEffectiveMavenRepoUrl(cfg) + "/" + groupPath + "/" + cfg.getArtifactId(); } private RepoConfig findConfig(String repoName) { @@ -136,13 +135,19 @@ public class MavenJavadocService { .orElseThrow(() -> new IllegalArgumentException("No config found for: " + repoName)); } - private String firstText(Document doc, String tagName) { - NodeList nodes = doc.getElementsByTagName(tagName); + private static Optional resolveVersionTag(Document doc) { + 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; } - private String firstText(org.w3c.dom.Element parent, String tagName) { - NodeList nodes = parent.getElementsByTagName(tagName); - return nodes.getLength() > 0 ? nodes.item(0).getTextContent() : null; + private static Document parseXml(String xml) + throws ParserConfigurationException, SAXException, IOException { + return DocumentBuilderFactory.newInstance() + .newDocumentBuilder() + .parse(new InputSource(new StringReader(xml))); } } diff --git a/src/main/java/codes/thischwa/jdvs/service/UpdateScheduler.java b/src/main/java/codes/thischwa/jdvs/service/UpdateScheduler.java index 5e654ba..c396d9b 100644 --- a/src/main/java/codes/thischwa/jdvs/service/UpdateScheduler.java +++ b/src/main/java/codes/thischwa/jdvs/service/UpdateScheduler.java @@ -3,7 +3,6 @@ package codes.thischwa.jdvs.service; import codes.thischwa.jdvs.config.JdvsConfig; import codes.thischwa.jdvs.jpa.GitRepositoryRepository; import codes.thischwa.jdvs.model.GitRepository; -import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -47,10 +46,16 @@ public class UpdateScheduler { return; } 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); } catch (IOException e) { - log.error("Failed to delete base directory: {}", baseDir, e); + log.error("Failed to walk base directory: {}", baseDir, e); } }