diff --git a/pom.xml b/pom.xml index 687846f988a65aa9dd87b90ae5b46d9e52fc8e30..773d7c2eb432f03a9daba77df6dc4667ff08fc65 100644 --- a/pom.xml +++ b/pom.xml @@ -23,6 +23,16 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-webflux</artifactId> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <optional>true</optional> + </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> @@ -53,4 +63,40 @@ </plugin> </plugins> </build> + + <distributionManagement> + <repository> + <id>release</id> + <name>Internal-release</name> + <url>https://maven.thecodelabs.de/artifactory/Internal-release</url> + </repository> + <snapshotRepository> + <id>snapshots</id> + <name>Internal-snapshots</name> + <url>https://maven.thecodelabs.de/artifactory/Internal-snapshots</url> + </snapshotRepository> + </distributionManagement> + + <repositories> + <repository> + <id>release</id> + <url>https://maven.thecodelabs.de/artifactory/Releases</url> + <releases> + <enabled>true</enabled> + </releases> + <snapshots> + <enabled>false</enabled> + </snapshots> + </repository> + <repository> + <id>snapshots</id> + <url>https://maven.thecodelabs.de/artifactory/Snapshots</url> + <releases> + <enabled>false</enabled> + </releases> + <snapshots> + <enabled>true</enabled> + </snapshots> + </repository> + </repositories> </project> diff --git a/src/main/java/de/tobias/playwall/controller/PluginDescriptionController.java b/src/main/java/de/tobias/playwall/controller/PluginController.java similarity index 56% rename from src/main/java/de/tobias/playwall/controller/PluginDescriptionController.java rename to src/main/java/de/tobias/playwall/controller/PluginController.java index 3683f293f8db59c4423ba7d7455a4ea39c7119fe..17868ff7cf1191ff02f89bfed03c062ab8d5a318 100644 --- a/src/main/java/de/tobias/playwall/controller/PluginDescriptionController.java +++ b/src/main/java/de/tobias/playwall/controller/PluginController.java @@ -1,7 +1,7 @@ package de.tobias.playwall.controller; -import de.tobias.playwall.model.PluginDescription; -import de.tobias.playwall.repository.PluginDescriptionRepository; +import de.tobias.playwall.model.Plugin; +import de.tobias.playwall.service.PluginService; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -12,13 +12,13 @@ import java.util.List; @RestController @RequestMapping("/plugins") @AllArgsConstructor -public class PluginDescriptionController +public class PluginController { - private final PluginDescriptionRepository repository; + private final PluginService service; @GetMapping - List<PluginDescription> getAllPlugins() + List<Plugin> getAllPlugins() { - return repository.findAll(); + return service.getAllPlugins(); } } diff --git a/src/main/java/de/tobias/playwall/infrastructure/ArtifactoryConfigurationProperties.java b/src/main/java/de/tobias/playwall/infrastructure/ArtifactoryConfigurationProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..1c97627ba566f3548c9ffbf6de902456485f84f0 --- /dev/null +++ b/src/main/java/de/tobias/playwall/infrastructure/ArtifactoryConfigurationProperties.java @@ -0,0 +1,20 @@ +package de.tobias.playwall.infrastructure; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties("playwall.artifactory") +@Getter +@Setter +public class ArtifactoryConfigurationProperties +{ + private String baseUrl; + private String username; + private String password; + + private String repository; + private String groupId; +} diff --git a/src/main/java/de/tobias/playwall/infrastructure/ArtifactoryWebClientBean.java b/src/main/java/de/tobias/playwall/infrastructure/ArtifactoryWebClientBean.java new file mode 100644 index 0000000000000000000000000000000000000000..4880151012fbb4566fa5747d71f53e7a06519036 --- /dev/null +++ b/src/main/java/de/tobias/playwall/infrastructure/ArtifactoryWebClientBean.java @@ -0,0 +1,24 @@ +package de.tobias.playwall.infrastructure; + +import lombok.AllArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication; + +@Component +@AllArgsConstructor +public class ArtifactoryWebClientBean +{ + private final ArtifactoryConfigurationProperties configurationProperties; + + @Bean + WebClient artifactoryWebClient() + { + return WebClient.builder() + .filter(basicAuthentication(configurationProperties.getUsername(), configurationProperties.getPassword())) + .baseUrl(configurationProperties.getBaseUrl()) + .build(); + } +} diff --git a/src/main/java/de/tobias/playwall/model/Plugin.java b/src/main/java/de/tobias/playwall/model/Plugin.java new file mode 100644 index 0000000000000000000000000000000000000000..ad56937a1e12de5247f08e70780389a118df2322 --- /dev/null +++ b/src/main/java/de/tobias/playwall/model/Plugin.java @@ -0,0 +1,19 @@ +package de.tobias.playwall.model; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class Plugin +{ + private String name; + private String displayName; + private String icon; + private String description; + + private String version; + private Integer build; +} diff --git a/src/main/java/de/tobias/playwall/model/PluginManifest.java b/src/main/java/de/tobias/playwall/model/PluginManifest.java new file mode 100644 index 0000000000000000000000000000000000000000..1b9f55ed2ebab57b3ed3012bfcec0be6531ed0a3 --- /dev/null +++ b/src/main/java/de/tobias/playwall/model/PluginManifest.java @@ -0,0 +1,14 @@ +package de.tobias.playwall.model; + +import lombok.Data; + +@Data +public class PluginManifest +{ + private String main; + private String name; + private String artifactId; + private String groupId; + private String version; + private Integer build; +} diff --git a/src/main/java/de/tobias/playwall/model/artifactory/Folder.java b/src/main/java/de/tobias/playwall/model/artifactory/Folder.java new file mode 100644 index 0000000000000000000000000000000000000000..beec1ab16707fd0f6f37dd42c5e757016e7d3765 --- /dev/null +++ b/src/main/java/de/tobias/playwall/model/artifactory/Folder.java @@ -0,0 +1,32 @@ +package de.tobias.playwall.model.artifactory; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +public class Folder +{ + public static final String ISO_DATE_TIME = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; + + @Data + public static class FolderItem { + private String uri; + private boolean folder; + } + + private String uri; + private String repo; + private String path; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = ISO_DATE_TIME) + private LocalDateTime created; + private String createdBy; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = ISO_DATE_TIME) + private LocalDateTime lastModified; + private String modifiedBy; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = ISO_DATE_TIME) + private LocalDateTime lastUpdated; + private List<FolderItem> children; +} diff --git a/src/main/java/de/tobias/playwall/model/artifactory/Version.java b/src/main/java/de/tobias/playwall/model/artifactory/Version.java new file mode 100644 index 0000000000000000000000000000000000000000..6441decf3ffa1cfa47d581c53bcb79a0b7945e1b --- /dev/null +++ b/src/main/java/de/tobias/playwall/model/artifactory/Version.java @@ -0,0 +1,15 @@ +package de.tobias.playwall.model.artifactory; + +public record Version(int major, int minor, int patch, boolean snapshot) +{ + public String toVersionString() + { + String version = String.format("%d.%d.%d", major, minor, patch); + if(snapshot) + { + version += "-SNAPSHOT"; + } + return version; + } + +} diff --git a/src/main/java/de/tobias/playwall/service/ArtifactoryClient.java b/src/main/java/de/tobias/playwall/service/ArtifactoryClient.java new file mode 100644 index 0000000000000000000000000000000000000000..0680ed3b3eb4477e39ecadb205a37c6086062d66 --- /dev/null +++ b/src/main/java/de/tobias/playwall/service/ArtifactoryClient.java @@ -0,0 +1,92 @@ +package de.tobias.playwall.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import de.tobias.playwall.infrastructure.ArtifactoryConfigurationProperties; +import de.tobias.playwall.model.PluginDescription; +import de.tobias.playwall.model.PluginManifest; +import de.tobias.playwall.model.artifactory.Folder; +import de.tobias.playwall.model.artifactory.Version; +import jakarta.annotation.PostConstruct; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; + +import java.util.Comparator; +import java.util.List; + +@Service +@AllArgsConstructor +public class ArtifactoryClient +{ + private final WebClient webClient; + private final ArtifactoryConfigurationProperties configurationProperties; + + private final VersionTokenizer versionTokenizer; + + private ObjectMapper mapper; + + @PostConstruct + private void init() + { + mapper = new ObjectMapper(new YAMLFactory()); + mapper.findAndRegisterModules(); + } + + public Version getLatestVersion(PluginDescription plugin) + { + final ResponseEntity<Folder> folderResponse = webClient.get() + .uri("/artifactory/api/storage/%s/%s/%s".formatted(configurationProperties.getRepository(), configurationProperties.getGroupId().replace(".", "/"), plugin.getName())) + .retrieve() + .toEntity(Folder.class) + .block(); + if(folderResponse == null || folderResponse.getBody() == null) + { + return null; + } + final List<Version> version = folderResponse.getBody().getChildren().stream() + .filter(Folder.FolderItem::isFolder) + .map(child -> versionTokenizer.getVersion(child.getUri())) + .sorted(Comparator.comparing(Version::major).thenComparing(Version::minor).thenComparing(Version::patch)) + .toList(); + return version.getLast(); + } + + private record ArchiveViewSourceRequest(String archivePath, String repoKey, String sourcePath) + { + } + + private record ArchiveViewSourceResponse(String source) + { + } + + @SneakyThrows + public PluginManifest getPluginManifest(PluginDescription description, Version version) + { + final ResponseEntity<ArchiveViewSourceResponse> response = webClient.post() + .uri("/ui/api/v1/ui/archiveViewSource") + .header("X-Requested-With", "XMLHttpRequest") + .bodyValue(new ArchiveViewSourceRequest( + "%s/%s/%s/%s-%s.jar".formatted( + configurationProperties.getGroupId().replace(".", "/"), + description.getName(), + version.toVersionString(), + description.getName(), + version.toVersionString() + ), + configurationProperties.getRepository(), + "plugin.yml" + )) + .retrieve() + .toEntity(ArchiveViewSourceResponse.class) + .block(); + + if(response == null || response.getBody() == null) + { + return null; + } + return mapper.readValue(response.getBody().source(), PluginManifest.class); + } +} diff --git a/src/main/java/de/tobias/playwall/service/PluginService.java b/src/main/java/de/tobias/playwall/service/PluginService.java new file mode 100644 index 0000000000000000000000000000000000000000..9a1f15be8e3c7f78077b03fa0b057c5570af4f71 --- /dev/null +++ b/src/main/java/de/tobias/playwall/service/PluginService.java @@ -0,0 +1,35 @@ +package de.tobias.playwall.service; + +import de.tobias.playwall.model.Plugin; +import de.tobias.playwall.model.PluginManifest; +import de.tobias.playwall.model.artifactory.Version; +import de.tobias.playwall.repository.PluginDescriptionRepository; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@AllArgsConstructor +public class PluginService +{ + private final PluginDescriptionRepository pluginDescriptionRepository; + private final ArtifactoryClient artifactoryClient; + + public List<Plugin> getAllPlugins() + { + return pluginDescriptionRepository.findAll().stream().map(pluginDescription -> { + final Version version = artifactoryClient.getLatestVersion(pluginDescription); + final PluginManifest manifest = artifactoryClient.getPluginManifest(pluginDescription, version); + + return Plugin.builder() + .name(pluginDescription.getName()) + .displayName(pluginDescription.getDisplayName()) + .description(pluginDescription.getDescription()) + .icon(pluginDescription.getIcon()) + .version(version.toVersionString()) + .build(manifest.getBuild()) + .build(); + }).toList(); + } +} diff --git a/src/main/java/de/tobias/playwall/service/VersionTokenizer.java b/src/main/java/de/tobias/playwall/service/VersionTokenizer.java new file mode 100644 index 0000000000000000000000000000000000000000..893bb0c8511032d6d3c7d0b9183cb6a5d2084eff --- /dev/null +++ b/src/main/java/de/tobias/playwall/service/VersionTokenizer.java @@ -0,0 +1,45 @@ +package de.tobias.playwall.service; + +import de.tobias.playwall.model.artifactory.Version; +import org.springframework.stereotype.Service; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Service +public class VersionTokenizer +{ + private VersionTokenizer() + { + } + + public Version getVersion(String value) + { + final Pattern pattern = Pattern.compile("(\\d+).(\\d+).(\\d+)(-SNAPSHOT)?"); + final Matcher matcher = pattern.matcher(value); + + if(matcher.find()) + { + final int major = Integer.parseInt(matcher.group(1)); + final int minor = Integer.parseInt(matcher.group(2)); + final int fix = Integer.parseInt(matcher.group(3)); + final boolean snapshot = matcher.group(4) != null; + + return new Version(major, minor, fix, snapshot); + } + return null; + } + + public int getRevision(String value) + { + final Pattern pattern = Pattern.compile("\\w*-[\\d.]*-[\\d]{8}.[\\d]{6}-(\\d*).[\\w]*"); + final Matcher matcher = pattern.matcher(value); + + if(matcher.find()) + { + return Integer.parseInt(matcher.group(1)); + } + return 0; + } + +}