From 04f8e8c16cfca314398eef4ee2b5dc59dfbd2942 Mon Sep 17 00:00:00 2001 From: Holger Veltrup Date: Tue, 3 Dec 2024 15:31:18 +0100 Subject: [PATCH] first commit --- .githooks/commit-msg | 24 + .github/dependabot.yml | 11 + .github/workflows/create-github-release.yml | 11 + .github/workflows/create-release.yml | 17 + .github/workflows/deploy-snapshot.yml | 16 + .github/workflows/start-hotfix.yml | 10 + .github/workflows/verify.yml | 12 + .gitignore | 13 + LICENSE.md | 21 + README.md | 14 + linkchecker/pom.xml | 81 ++++ .../domain/entity/DailyScheduling.java | 76 ++++ .../LinkCheckerBackgroundExecution.java | 134 ++++++ .../domain/entity/LinkCheckerConfig.java | 127 ++++++ .../entity/LinkCheckerExcludePattern.java | 3 + .../entity/LinkCheckerExcludePatternType.java | 7 + .../domain/entity/LinkCheckerLink.java | 89 ++++ .../domain/entity/LinkCheckerLinkFilter.java | 119 +++++ .../domain/entity/LinkCheckerResult.java | 131 ++++++ .../domain/entity/LinkCheckerResultItem.java | 140 ++++++ .../entity/LinkCheckerResultStatistic.java | 76 ++++ .../domain/entity/PublishedExternalLink.java | 107 +++++ .../linkchecker/domain/entity/Scheduling.java | 15 + .../linkchecker/domain/entity/StatusType.java | 13 + .../domain/entity/StatusTypeCount.java | 3 + .../exception/AccessDeniedException.java | 13 + .../exception/LinkCheckerException.java | 18 + .../domain/exception/PublisherException.java | 18 + .../core/linkchecker/port/AccessControl.java | 11 + .../core/linkchecker/port/LinkChecker.java | 8 + .../port/LinkCheckerBackgroundExecutor.java | 7 + .../port/LinkCheckerConfigStore.java | 9 + .../port/LinkCheckerScheduler.java | 5 + .../port/PublishedExternalLinkRepository.java | 26 ++ .../LinkCheckerExcludesPatternMatcher.java | 51 +++ .../usecase/BackgroundLinkCheck.java | 83 ++++ .../linkchecker/usecase/GetCheckResult.java | 30 ++ .../usecase/GetCheckResultsStatistic.java | 29 ++ .../usecase/GetLinkCheckerConfig.java | 29 ++ .../usecase/LinkCheckByHashes.java | 95 ++++ .../usecase/StoreLinkCheckerConfig.java | 37 ++ linkchecker/src/main/java/module-info.java | 15 + .../domain/entity/LinkCheckerConfigTest.java | 65 +++ .../entity/LinkCheckerResultItemTest.java | 13 + .../entity/PublishedExternalLinkTest.java | 36 ++ pmd-ruleset.xml | 367 +++++++++++++++ pom.xml | 423 ++++++++++++++++++ spotbug-exclude-filter.xml | 14 + 48 files changed, 2672 insertions(+) create mode 100755 .githooks/commit-msg create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/create-github-release.yml create mode 100644 .github/workflows/create-release.yml create mode 100644 .github/workflows/deploy-snapshot.yml create mode 100644 .github/workflows/start-hotfix.yml create mode 100644 .github/workflows/verify.yml create mode 100644 .gitignore create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 linkchecker/pom.xml create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/DailyScheduling.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerBackgroundExecution.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerConfig.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerExcludePattern.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerExcludePatternType.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerLink.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerLinkFilter.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerResult.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerResultItem.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerResultStatistic.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/PublishedExternalLink.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/Scheduling.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/StatusType.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/StatusTypeCount.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/exception/AccessDeniedException.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/exception/LinkCheckerException.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/exception/PublisherException.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/AccessControl.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/LinkChecker.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/LinkCheckerBackgroundExecutor.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/LinkCheckerConfigStore.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/LinkCheckerScheduler.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/PublishedExternalLinkRepository.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/service/LinkCheckerExcludesPatternMatcher.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/BackgroundLinkCheck.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/GetCheckResult.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/GetCheckResultsStatistic.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/GetLinkCheckerConfig.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/LinkCheckByHashes.java create mode 100644 linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/StoreLinkCheckerConfig.java create mode 100644 linkchecker/src/main/java/module-info.java create mode 100644 linkchecker/src/test/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerConfigTest.java create mode 100644 linkchecker/src/test/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerResultItemTest.java create mode 100644 linkchecker/src/test/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/PublishedExternalLinkTest.java create mode 100644 pmd-ruleset.xml create mode 100644 pom.xml create mode 100644 spotbug-exclude-filter.xml diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100755 index 0000000..b5c5f08 --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# Create a regex for a conventional commit. +convetional_commit_regex="^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z \-]+\))?!?: .+$" + +# Get the commit message (the parameter we're given is just the path to the +# temporary file which holds the message). +commit_message=$(cat "$1") + +# Check the message, if we match, all good baby. +if [[ "$commit_message" =~ $convetional_commit_regex ]]; then + echo -e "\e[32mCommit message meets Conventional Commit standards...\e[0m" + exit 0 +fi + +# Uh-oh, this is not a conventional commit, show an example and link to the spec. +echo -e "\e[31mThe commit message does not meet the Conventional Commit standard\e[0m" +echo "An example of a valid message is: " +echo " feat(login): add the 'remember me' button" +echo "More details at: https://www.conventionalcommits.org/en/v1.0.0/#summary" +exit 1 + + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..d4cd657 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "maven" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/create-github-release.yml b/.github/workflows/create-github-release.yml new file mode 100644 index 0000000..5bfc215 --- /dev/null +++ b/.github/workflows/create-github-release.yml @@ -0,0 +1,11 @@ +name: (📡) Create GitHub Release + +on: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+' + workflow_dispatch: + +jobs: + create-github-release: + uses: sitepark/github-project-workflow/.github/workflows/create-github-release.yml@release/1.x diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 0000000..df58698 --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,17 @@ +name: (▶) Create Release + +on: workflow_dispatch + +jobs: + release: + uses: sitepark/github-project-workflow/.github/workflows/maven-release.yml@release/1.x + with: + botName: "sitepark-bot" + botEmail: "opensource@sitepark.com" + secrets: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} + GPG_KEY: ${{ secrets.GPG_KEY }} + MVN_REPO_RELEASE_SERVER_ID: ${{ secrets.MVN_REPO_RELEASE_SERVER_ID }} + MVN_REPO_RELEASE_SERVER_USERNAME: ${{ secrets.MVN_REPO_RELEASE_SERVER_USERNAME }} + MVN_REPO_RELEASE_SERVER_PASSWORD: ${{ secrets.MVN_REPO_RELEASE_SERVER_PASSWORD }} diff --git a/.github/workflows/deploy-snapshot.yml b/.github/workflows/deploy-snapshot.yml new file mode 100644 index 0000000..9f9e20f --- /dev/null +++ b/.github/workflows/deploy-snapshot.yml @@ -0,0 +1,16 @@ +name: (📡) Deploy Snapshot + +on: + push: + branches: + - "main" + workflow_dispatch: + +jobs: + deploy: + uses: sitepark/github-project-workflow/.github/workflows/maven-deploy-snapshot.yml@release/1.x + secrets: + MVN_REPO_SNAPSHOT_SERVER_ID: ${{ secrets.MVN_REPO_SNAPSHOT_SERVER_ID }} + MVN_REPO_SNAPSHOT_SERVER_USERNAME: ${{ secrets.MVN_REPO_SNAPSHOT_SERVER_USERNAME }} + MVN_REPO_SNAPSHOT_SERVER_PASSWORD: ${{ secrets.MVN_REPO_SNAPSHOT_SERVER_PASSWORD }} + MVN_REPO_SNAPSHOT_SERVER_URL: ${{ secrets.MVN_REPO_SNAPSHOT_SERVER_URL }} diff --git a/.github/workflows/start-hotfix.yml b/.github/workflows/start-hotfix.yml new file mode 100644 index 0000000..58ff095 --- /dev/null +++ b/.github/workflows/start-hotfix.yml @@ -0,0 +1,10 @@ +name: (▶) Start Hotfix + +on: workflow_dispatch + +jobs: + start-hotfix: + uses: sitepark/github-project-workflow/.github/workflows/maven-start-hotfix.yml@release/1.x + with: + ref: ${{ github.ref }} + diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml new file mode 100644 index 0000000..23f3e88 --- /dev/null +++ b/.github/workflows/verify.yml @@ -0,0 +1,12 @@ +name: (📡) Verify + +on: + push: + branches-ignore: + - 'main' + workflow_dispatch: + +jobs: + verify: + uses: sitepark/github-project-workflow/.github/workflows/maven-verify.yml@release/1.x + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6f3055a --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# eclipse +.classpath +.project +.settings/ +.fbExcludeFilterFile +.pmd +.pmdruleset.xml + + +# ? validate +reports/ + +target/ diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..4188ec7 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Sitepark GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..58fef54 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# Publisher + +The Publisher component of the IES takes on the task of publishing data by compiling data from a resource with the help of aggregators and writing it as a file to the file system. + +These files are made available to the web server, with which these resources can then be further processed for the website. + +Other functions that are also assigned to this Publisher component are linked to the publication process. + +## Link Checker + +For an aggregated resource, all external links are collected and stored in the database. A scheduler checks the validity of the links at regular intervals and updates the status in the database. +The status can be determined via the CMS to give the editors the opportunity to correct the links. + +## UseCases diff --git a/linkchecker/pom.xml b/linkchecker/pom.xml new file mode 100644 index 0000000..b6d3ce2 --- /dev/null +++ b/linkchecker/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + + + com.sitepark.ies + ies-publisher-core + 1.0.0-SNAPSHOT + + + ies-publisher-core-linkchecker + IES Link Checker + Check published links + + + + + com.github.spotbugs + spotbugs-annotations + + + + jakarta.inject + jakarta.inject-api + + + + com.fasterxml.jackson.core + jackson-databind + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + + + + org.eclipse.jdt + org.eclipse.jdt.annotation + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.hamcrest + hamcrest-library + test + + + org.mockito + mockito-core + test + + + nl.jqno.equalsverifier + equalsverifier + test + + + com.jparams + to-string-verifier + test + + + diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/DailyScheduling.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/DailyScheduling.java new file mode 100644 index 0000000..d5a76c2 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/DailyScheduling.java @@ -0,0 +1,76 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.entity; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import java.time.LocalTime; +import java.util.Objects; + +@JsonDeserialize(builder = DailyScheduling.Builder.class) +public class DailyScheduling implements Scheduling { + + private final LocalTime startTime; + + protected DailyScheduling(Builder builder) { + this.startTime = builder.startTime; + } + + @Override + public String getType() { + return "daily"; + } + + @JsonFormat(pattern = "HH:mm") + public LocalTime getStartTime() { + return this.startTime; + } + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public int hashCode() { + return Objects.hash(this.startTime); + } + + @Override + public boolean equals(Object o) { + + if (!(o instanceof DailyScheduling that)) { + return false; + } + + return Objects.equals(this.startTime, that.startTime); + } + + @JsonPOJOBuilder(withPrefix = "", buildMethodName = "build") + public static class Builder { + + private LocalTime startTime; + + private Builder() {} + + private Builder(DailyScheduling instance) { + this.startTime = instance.startTime; + } + + public Builder startTime(LocalTime startTime) { + this.startTime = startTime; + return this; + } + + public Builder startTime(int hour, int minute) { + this.startTime = LocalTime.of(hour, minute); + return this; + } + + public DailyScheduling build() { + return new DailyScheduling(this); + } + } +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerBackgroundExecution.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerBackgroundExecution.java new file mode 100644 index 0000000..14ac6e6 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerBackgroundExecution.java @@ -0,0 +1,134 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.entity; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; + +public class LinkCheckerBackgroundExecution { + + private final int parallel; + + private final String[] topic; + + private final List links; + + private final Consumer operation; + + protected LinkCheckerBackgroundExecution(Builder builder) { + this.parallel = builder.parallel; + this.topic = builder.topic; + this.links = Collections.unmodifiableList(builder.links); + this.operation = builder.operation; + } + + public int getParallel() { + return this.parallel; + } + + @SuppressFBWarnings("EI_EXPOSE_REP") + public String[] getTopic() { + return this.topic.clone(); + } + + public List getLinks() { + return this.links; + } + + public Consumer getOperation() { + return this.operation; + } + + @Override + public final int hashCode() { + return Objects.hash(Arrays.hashCode(this.topic)); + } + + @Override + public final boolean equals(Object o) { + + if (!(o instanceof LinkCheckerBackgroundExecution that)) { + return false; + } + + return Arrays.equals(this.topic, that.topic); + } + + @Override + public String toString() { + StringBuilder b = + new StringBuilder(100) + .append("EntityBackgroundExecution[topic:") + .append(Arrays.toString(this.topic)) + .append(']'); + return b.toString(); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private int parallel; + + private String[] topic; + + private final List links = new ArrayList<>(); + + private Consumer operation; + + private Builder() {} + + public Builder parallel(int parallel) { + this.parallel = parallel; + return this; + } + + /** + * Topics are used to display all background operations for a specific topic. Topics are + * hierarchical and the path of the topic is specified via a string array. Topics are freely + * definable. If e.g. all Topics of level1 are queried, all BackgroundExecutions + * recursively below level1 are returned. + */ + public Builder topic(String... topic) { + Objects.requireNonNull(topic, "topic is null"); + for (String part : topic) { + Objects.requireNonNull(part, "operations contains null values"); + } + + this.topic = topic.clone(); + return this; + } + + public Builder links(List links) { + Objects.requireNonNull(links, "links is null"); + for (LinkCheckerLink link : links) { + this.link(link); + } + return this; + } + + public Builder link(LinkCheckerLink link) { + Objects.requireNonNull(link, "link is null"); + this.links.add(link); + return this; + } + + public Builder operation(Consumer operation) { + Objects.requireNonNull(operation, "operation is null"); + this.operation = operation; + return this; + } + + public LinkCheckerBackgroundExecution build() { + if (this.topic == null) { + throw new IllegalStateException("topic must be set"); + } + return new LinkCheckerBackgroundExecution(this); + } + } +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerConfig.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerConfig.java new file mode 100644 index 0000000..ca16f52 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerConfig.java @@ -0,0 +1,127 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.entity; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +@JsonDeserialize(builder = LinkCheckerConfig.Builder.class) +public final class LinkCheckerConfig { + + private final int parallel; + + private final int timeout; + + private final Scheduling scheduling; + + private final List excludes; + + protected LinkCheckerConfig(Builder builder) { + this.parallel = builder.parallel; + this.timeout = builder.timeout; + this.scheduling = builder.scheduling; + this.excludes = builder.excludes; + } + + public int getParallel() { + return this.parallel; + } + + public int getTimeout() { + return this.timeout; + } + + public Scheduling getScheduling() { + return this.scheduling; + } + + public List getExcludes() { + return Collections.unmodifiableList(this.excludes); + } + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public int hashCode() { + return Objects.hash(this.timeout, this.parallel, this.scheduling, this.excludes); + } + + @Override + public boolean equals(Object o) { + + if (!(o instanceof LinkCheckerConfig that)) { + return false; + } + + return Objects.equals(this.timeout, that.timeout) + && Objects.equals(this.parallel, that.parallel) + && Objects.equals(this.scheduling, that.scheduling) + && Objects.equals(this.excludes, that.excludes); + } + + @JsonPOJOBuilder(withPrefix = "", buildMethodName = "build") + public static class Builder { + + private int parallel; + + private int timeout; + + public Scheduling scheduling; + + private final List excludes = new ArrayList<>(); + + protected Builder() {} + + protected Builder(LinkCheckerConfig instance) { + this.timeout = instance.timeout; + this.excludes.addAll(instance.excludes); + } + + public Builder scheduling(Scheduling scheduling) { + this.scheduling = scheduling; + return this; + } + + public Builder parallel(int parallel) { + if (parallel < 1) { + throw new IllegalArgumentException("Parallel must be greater than 0"); + } + if (parallel > 5) { + throw new IllegalArgumentException("Parallel must not be greater than 5"); + } + this.parallel = parallel; + return this; + } + + public Builder timeout(int timeout) { + this.timeout = timeout; + return this; + } + + public Builder excludes(List excludes) { + Objects.requireNonNull(excludes); + for (LinkCheckerExcludePattern exclude : excludes) { + this.exclude(exclude); + } + return this; + } + + public Builder exclude(LinkCheckerExcludePattern exclude) { + Objects.requireNonNull(exclude); + this.excludes.add(exclude); + return this; + } + + public LinkCheckerConfig build() { + return new LinkCheckerConfig(this); + } + } +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerExcludePattern.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerExcludePattern.java new file mode 100644 index 0000000..4192736 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerExcludePattern.java @@ -0,0 +1,3 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.entity; + +public record LinkCheckerExcludePattern(LinkCheckerExcludePatternType type, String pattern) {} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerExcludePatternType.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerExcludePatternType.java new file mode 100644 index 0000000..60513d4 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerExcludePatternType.java @@ -0,0 +1,7 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.entity; + +public enum LinkCheckerExcludePatternType { + REGEX, + CONTAINS, + GLOB +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerLink.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerLink.java new file mode 100644 index 0000000..185373f --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerLink.java @@ -0,0 +1,89 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.entity; + +import java.util.Objects; + +public class LinkCheckerLink { + + private final String hash; + + private final String url; + + private final int timeout; + + protected LinkCheckerLink(Builder builder) { + this.hash = builder.hash; + this.url = builder.url; + this.timeout = builder.timeout; + } + + public String getHash() { + return this.hash; + } + + public String getUrl() { + return this.url; + } + + public int getTimeout() { + return this.timeout; + } + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public int hashCode() { + return Objects.hash(this.hash, this.url); + } + + @Override + public boolean equals(Object o) { + + if (!(o instanceof LinkCheckerLink that)) { + return false; + } + + return Objects.equals(this.hash, that.hash) && Objects.equals(this.url, that.url); + } + + public static class Builder { + + private String hash; + + private String url; + + private int timeout; + + private Builder() {} + + private Builder(LinkCheckerLink instance) { + this.hash = instance.hash; + this.url = instance.url; + this.timeout = instance.timeout; + } + + public Builder hash(String hash) { + this.hash = hash; + return this; + } + + public Builder url(String url) { + this.url = url; + return this; + } + + public Builder timeout(int timeout) { + this.timeout = timeout; + return this; + } + + public LinkCheckerLink build() { + return new LinkCheckerLink(this); + } + } +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerLinkFilter.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerLinkFilter.java new file mode 100644 index 0000000..4a86b3f --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerLinkFilter.java @@ -0,0 +1,119 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.entity; + +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class LinkCheckerLinkFilter { + + private final List terms; + + private final List statusTypes; + + private LinkCheckerLinkFilter(Builder builder) { + this.terms = Collections.unmodifiableList(builder.terms); + this.statusTypes = Collections.unmodifiableList(builder.statusTypes); + } + + public List getTerms() { + return this.terms; + } + + public List getStatusTypes() { + return this.statusTypes; + } + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public int hashCode() { + return Objects.hash(this.terms, this.statusTypes); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof LinkCheckerLinkFilter that)) { + return false; + } + return Objects.equals(this.terms, that.terms) + && Objects.equals(this.statusTypes, that.statusTypes); + } + + @Override + public String toString() { + return "LinkCheckerLinkFilter{" + + "terms='" + + this.terms + + '\'' + + ", statusTypes=" + + this.statusTypes + + '}'; + } + + @JsonPOJOBuilder(withPrefix = "", buildMethodName = "build") + public static class Builder { + + private List terms = new ArrayList<>(); + + private final List statusTypes = new ArrayList<>(); + + private Builder() {} + + private Builder(LinkCheckerLinkFilter instance) { + this.terms = new ArrayList<>(instance.terms); + this.statusTypes.addAll(instance.statusTypes); + } + + public Builder terms(Collection terms) { + Objects.requireNonNull(terms, "terms is null"); + this.terms.clear(); + for (String term : terms) { + this.term(term); + } + return this; + } + + public Builder terms(String[] terms) { + Objects.requireNonNull(terms, "terms is null"); + this.terms.clear(); + for (String term : terms) { + this.term(term); + } + return this; + } + + public Builder term(String term) { + Objects.requireNonNull(term, "term is null"); + this.terms.add(term); + return this; + } + + public Builder statusTypes(List statusTypes) { + Objects.requireNonNull(statusTypes, "statusTypes is null"); + this.statusTypes.clear(); + for (StatusType statusType : statusTypes) { + this.statusType(statusType); + } + return this; + } + + public Builder statusType(StatusType statusType) { + Objects.requireNonNull(statusType, "statusType is null"); + this.statusTypes.add(statusType); + return this; + } + + public LinkCheckerLinkFilter build() { + return new LinkCheckerLinkFilter(this); + } + } +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerResult.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerResult.java new file mode 100644 index 0000000..9d25fed --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerResult.java @@ -0,0 +1,131 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.entity; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class LinkCheckerResult { + + private final List items; + + private final int total; + + private final int start; + + private final int limit; + + public LinkCheckerResult(Builder builder) { + this.items = Collections.unmodifiableList(builder.items); + this.total = builder.total; + this.start = builder.start; + this.limit = builder.limit; + } + + public List getItems() { + return items; + } + + public int getTotal() { + return total; + } + + public int getStart() { + return start; + } + + public int getLimit() { + return limit; + } + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public int hashCode() { + return Objects.hash(items, total, start, limit); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof LinkCheckerResult that)) { + return false; + } + return Objects.equals(this.items, that.items) + && Objects.equals(this.total, that.total) + && Objects.equals(this.start, that.start) + && Objects.equals(this.limit, that.limit); + } + + @Override + public String toString() { + return "LinkCheckerResult [items=" + + items + + ", total=" + + total + + ", start=" + + start + + ", limit=" + + limit + + "]"; + } + + public static class Builder { + + private final List items = new ArrayList<>(); + + private int total; + + private int start; + + private int limit; + + private Builder() {} + + private Builder(LinkCheckerResult instance) { + this.items.addAll(instance.items); + this.total = instance.total; + this.start = instance.start; + this.limit = instance.limit; + } + + public Builder items(List items) { + Objects.requireNonNull(items, "items must not be null"); + this.items.clear(); + for (LinkCheckerResultItem item : items) { + this.items.add(item); + } + return this; + } + + public Builder item(LinkCheckerResultItem item) { + Objects.requireNonNull(item, "item must not be null"); + this.items.add(item); + return this; + } + + public Builder total(int total) { + this.total = total; + return this; + } + + public Builder start(int start) { + this.start = start; + return this; + } + + public Builder limit(int limit) { + this.limit = limit; + return this; + } + + public LinkCheckerResult build() { + return new LinkCheckerResult(this); + } + } +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerResultItem.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerResultItem.java new file mode 100644 index 0000000..0112d33 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerResultItem.java @@ -0,0 +1,140 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.entity; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; + +public final class LinkCheckerResultItem { + private final String url; + private final String hash; + private final StatusType status; + private final String message; + private final List entities; + + protected LinkCheckerResultItem(Builder builder) { + this.url = builder.url; + this.hash = builder.hash; + this.status = builder.status; + this.message = builder.message; + this.entities = new ArrayList<>(builder.entities); + } + + public String getUrl() { + return this.url; + } + + public StatusType getStatus() { + return this.status; + } + + public String getMessage() { + return this.message; + } + + public List getEntities() { + return Collections.unmodifiableList(this.entities); + } + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public int hashCode() { + return Objects.hash(this.url, this.hash, this.status, this.message, this.entities); + } + + @Override + public boolean equals(Object o) { + + if (!(o instanceof LinkCheckerResultItem that)) { + return false; + } + + return Objects.equals(this.url, that.url) + && Objects.equals(this.hash, that.hash) + && Objects.equals(this.status, that.status) + && Objects.equals(this.message, that.message) + && Objects.equals(this.entities, that.entities); + } + + @Override + public String toString() { + return "LinkCheckerResultItem [url=" + + url + + ", hash=" + + hash + + ", status=" + + status + + ", message=" + + message + + ", entities=" + + entities + + "]"; + } + + public static class Builder { + + private String url; + private String hash; + private StatusType status; + private String message; + private final Collection entities = new HashSet<>(); + + private Builder() {} + + private Builder(LinkCheckerResultItem instance) { + this.url = instance.url; + this.hash = instance.hash; + this.status = instance.status; + this.message = instance.message; + this.entities.addAll(instance.entities); + } + + public Builder url(String url) { + this.url = url; + return this; + } + + public Builder hash(String hash) { + this.hash = hash; + return this; + } + + public Builder status(StatusType status) { + this.status = status; + return this; + } + + public Builder message(String message) { + this.message = message; + return this; + } + + public Builder entities(Collection entities) { + Objects.requireNonNull(entities, "entities is null"); + this.entities.clear(); + for (String entity : entities) { + this.entity(entity); + } + return this; + } + + public Builder entity(String entity) { + Objects.requireNonNull(entity, "entity is null"); + this.entities.add(entity); + return this; + } + + public LinkCheckerResultItem build() { + return new LinkCheckerResultItem(this); + } + } +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerResultStatistic.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerResultStatistic.java new file mode 100644 index 0000000..3dc371e --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerResultStatistic.java @@ -0,0 +1,76 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.entity; + +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class LinkCheckerResultStatistic { + + private final List statusCounts; + + private LinkCheckerResultStatistic(Builder builder) { + this.statusCounts = Collections.unmodifiableList(builder.statusCounts); + } + + public List getStatusCounts() { + return this.statusCounts; + } + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public int hashCode() { + return Objects.hash(this.statusCounts); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof LinkCheckerResultStatistic that)) { + return false; + } + return Objects.equals(this.statusCounts, that.statusCounts); + } + + @Override + public String toString() { + return "LinkCheckerResultStatistic [statusCounts=" + statusCounts + "]"; + } + + @JsonPOJOBuilder(withPrefix = "", buildMethodName = "build") + public static class Builder { + + private final List statusCounts = new ArrayList<>(); + + private Builder() {} + + private Builder(LinkCheckerResultStatistic instance) { + this.statusCounts.addAll(instance.statusCounts); + } + + public Builder statusCounts(List statusCounts) { + Objects.requireNonNull(statusCounts, "statusCounts is null"); + for (StatusTypeCount statusCount : statusCounts) { + this.statusCount(statusCount); + } + return this; + } + + public Builder statusCount(StatusTypeCount statusCounts) { + Objects.requireNonNull(statusCounts, "statusCounts is null"); + this.statusCounts.add(statusCounts); + return this; + } + + public LinkCheckerResultStatistic build() { + return new LinkCheckerResultStatistic(this); + } + } +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/PublishedExternalLink.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/PublishedExternalLink.java new file mode 100644 index 0000000..fae8054 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/PublishedExternalLink.java @@ -0,0 +1,107 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.entity; + +import java.util.Objects; + +public final class PublishedExternalLink { + + private final String entity; + + private final String channel; + + private final String section; + + private final String url; + + protected PublishedExternalLink(Builder builder) { + this.entity = builder.entity; + this.channel = builder.channel; + this.section = builder.section; + this.url = builder.url; + } + + public String getEntity() { + return this.entity; + } + + public String getChannel() { + return this.channel; + } + + public String getSection() { + return this.section; + } + + public String getUrl() { + return this.url; + } + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return new Builder(this); + } + + @Override + public int hashCode() { + return Objects.hash(this.entity, this.channel, this.section, this.url); + } + + @Override + public boolean equals(Object o) { + + if (!(o instanceof PublishedExternalLink that)) { + return false; + } + + return Objects.equals(this.entity, that.entity) + && Objects.equals(this.section, that.section) + && Objects.equals(this.channel, that.channel) + && Objects.equals(this.url, that.url); + } + + public static class Builder { + + private String entity; + + private String channel; + + private String section; + + private String url; + + private Builder() {} + + private Builder(PublishedExternalLink instance) { + this.entity = instance.entity; + this.channel = instance.channel; + this.section = instance.section; + this.url = instance.url; + } + + public Builder entity(String entity) { + this.entity = entity; + return this; + } + + public Builder channel(String channel) { + this.channel = channel; + return this; + } + + public Builder section(String section) { + this.section = section; + return this; + } + + public Builder url(String url) { + this.url = url; + return this; + } + + public PublishedExternalLink build() { + return new PublishedExternalLink(this); + } + } +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/Scheduling.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/Scheduling.java new file mode 100644 index 0000000..55b1f80 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/Scheduling.java @@ -0,0 +1,15 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.entity; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(name = "daily", value = DailyScheduling.class), +}) +public interface Scheduling { + String getType(); +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/StatusType.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/StatusType.java new file mode 100644 index 0000000..f2783ad --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/StatusType.java @@ -0,0 +1,13 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.entity; + +public enum StatusType { + OK, + UNAUTHORIZED, + FORBIDDEN, + NOT_FOUND, + INTERNAL_SERVER_ERROR, + TIMEOUT, + OTHER, + IGNORED, + UNKNOWN +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/StatusTypeCount.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/StatusTypeCount.java new file mode 100644 index 0000000..b682519 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/StatusTypeCount.java @@ -0,0 +1,3 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.entity; + +public record StatusTypeCount(StatusType statusType, int count) {} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/exception/AccessDeniedException.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/exception/AccessDeniedException.java new file mode 100644 index 0000000..b5b6c45 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/exception/AccessDeniedException.java @@ -0,0 +1,13 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.exception; + +public class AccessDeniedException extends PublisherException { + private static final long serialVersionUID = 1L; + + public AccessDeniedException(String message) { + super(message); + } + + public AccessDeniedException(String message, Throwable t) { + super(message, t); + } +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/exception/LinkCheckerException.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/exception/LinkCheckerException.java new file mode 100644 index 0000000..c579544 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/exception/LinkCheckerException.java @@ -0,0 +1,18 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.exception; + +public class LinkCheckerException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public LinkCheckerException() { + super(); + } + + public LinkCheckerException(String message) { + super(message); + } + + public LinkCheckerException(String message, Throwable t) { + super(message, t); + } +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/exception/PublisherException.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/exception/PublisherException.java new file mode 100644 index 0000000..7134a15 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/domain/exception/PublisherException.java @@ -0,0 +1,18 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.exception; + +public abstract class PublisherException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public PublisherException() { + super(); + } + + public PublisherException(String message) { + super(message); + } + + public PublisherException(String message, Throwable t) { + super(message, t); + } +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/AccessControl.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/AccessControl.java new file mode 100644 index 0000000..c1c516b --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/AccessControl.java @@ -0,0 +1,11 @@ +package com.sitepark.ies.publisher.core.linkchecker.port; + +public interface AccessControl { + boolean isAllowRunLinkChecker(); + + boolean isAllowStoreLinkCheckerConfig(); + + boolean isAllowGetLinkCheckerConfig(); + + boolean isAllowGetCheckResults(); +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/LinkChecker.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/LinkChecker.java new file mode 100644 index 0000000..57b71f6 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/LinkChecker.java @@ -0,0 +1,8 @@ +package com.sitepark.ies.publisher.core.linkchecker.port; + +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerLink; +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerResultItem; + +public interface LinkChecker { + LinkCheckerResultItem checkLink(LinkCheckerLink link); +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/LinkCheckerBackgroundExecutor.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/LinkCheckerBackgroundExecutor.java new file mode 100644 index 0000000..5ad13cd --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/LinkCheckerBackgroundExecutor.java @@ -0,0 +1,7 @@ +package com.sitepark.ies.publisher.core.linkchecker.port; + +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerBackgroundExecution; + +public interface LinkCheckerBackgroundExecutor { + String execute(LinkCheckerBackgroundExecution execution); +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/LinkCheckerConfigStore.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/LinkCheckerConfigStore.java new file mode 100644 index 0000000..b6606f7 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/LinkCheckerConfigStore.java @@ -0,0 +1,9 @@ +package com.sitepark.ies.publisher.core.linkchecker.port; + +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerConfig; + +public interface LinkCheckerConfigStore { + void store(LinkCheckerConfig config); + + LinkCheckerConfig get(); +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/LinkCheckerScheduler.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/LinkCheckerScheduler.java new file mode 100644 index 0000000..5f4e522 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/LinkCheckerScheduler.java @@ -0,0 +1,5 @@ +package com.sitepark.ies.publisher.core.linkchecker.port; + +public interface LinkCheckerScheduler { + void configChanged(); +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/PublishedExternalLinkRepository.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/PublishedExternalLinkRepository.java new file mode 100644 index 0000000..1133069 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/port/PublishedExternalLinkRepository.java @@ -0,0 +1,26 @@ +package com.sitepark.ies.publisher.core.linkchecker.port; + +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerLink; +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerLinkFilter; +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerResult; +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerResultItem; +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerResultStatistic; +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.PublishedExternalLink; +import java.util.Collection; +import java.util.List; + +public interface PublishedExternalLinkRepository { + void store(String channel, String entity, List links); + + List getLinks(); + + List getLinks(Collection hashes); + + LinkCheckerResult getCheckResult(LinkCheckerLinkFilter filter, int start, int limit); + + LinkCheckerResultStatistic getCheckResultsStatistic(); + + void updateCheckResult(String hash, LinkCheckerResultItem result); + + void cleanupUnusedLinks(); +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/service/LinkCheckerExcludesPatternMatcher.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/service/LinkCheckerExcludesPatternMatcher.java new file mode 100644 index 0000000..1188d98 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/service/LinkCheckerExcludesPatternMatcher.java @@ -0,0 +1,51 @@ +package com.sitepark.ies.publisher.core.linkchecker.service; + +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerExcludePattern; +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerExcludePatternType; +import java.nio.file.PathMatcher; +import java.util.ArrayList; +import java.util.List; + +public class LinkCheckerExcludesPatternMatcher { + + private final List patternList; + + public LinkCheckerExcludesPatternMatcher(List patternList) { + this.patternList = new ArrayList<>(patternList); + } + + public boolean isExcluded(String url) { + for (LinkCheckerExcludePattern pattern : this.patternList) { + if (this.matches(url, pattern)) { + return true; + } + } + return false; + } + + private boolean matches(String url, LinkCheckerExcludePattern pattern) { + switch (pattern.type()) { + case LinkCheckerExcludePatternType.REGEX: + return this.matchRegex(url, pattern.pattern()); + case LinkCheckerExcludePatternType.CONTAINS: + return this.matchContains(url, pattern.pattern()); + case LinkCheckerExcludePatternType.GLOB: + return this.matchGlob(url, pattern.pattern()); + } + return true; + } + + private boolean matchRegex(String url, String pattern) { + return url.matches(pattern); + } + + private boolean matchContains(String url, String pattern) { + return url.indexOf("pattern") != -1; + } + + private boolean matchGlob(String url, String pattern) { + PathMatcher pathMatcher = + java.nio.file.FileSystems.getDefault().getPathMatcher("glob:" + pattern); + return pathMatcher.matches(java.nio.file.Paths.get(url)); + } +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/BackgroundLinkCheck.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/BackgroundLinkCheck.java new file mode 100644 index 0000000..5e76496 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/BackgroundLinkCheck.java @@ -0,0 +1,83 @@ +package com.sitepark.ies.publisher.core.linkchecker.usecase; + +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerBackgroundExecution; +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerConfig; +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerLink; +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerResultItem; +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.StatusType; +import com.sitepark.ies.publisher.core.linkchecker.domain.exception.AccessDeniedException; +import com.sitepark.ies.publisher.core.linkchecker.port.AccessControl; +import com.sitepark.ies.publisher.core.linkchecker.port.LinkChecker; +import com.sitepark.ies.publisher.core.linkchecker.port.LinkCheckerBackgroundExecutor; +import com.sitepark.ies.publisher.core.linkchecker.port.LinkCheckerConfigStore; +import com.sitepark.ies.publisher.core.linkchecker.port.PublishedExternalLinkRepository; +import com.sitepark.ies.publisher.core.linkchecker.service.LinkCheckerExcludesPatternMatcher; +import jakarta.inject.Inject; +import java.util.List; + +public class BackgroundLinkCheck { + + private final AccessControl accessControl; + private final LinkChecker linkChecker; + private final LinkCheckerBackgroundExecutor linkCheckerBackgroundExecutor; + private final PublishedExternalLinkRepository publishedExternalLinkRepository; + private final LinkCheckerConfigStore linkCheckerConfigStore; + + @Inject + public BackgroundLinkCheck( + AccessControl accessControl, + LinkChecker linkChecker, + LinkCheckerBackgroundExecutor linkCheckerBackgroundExecutor, + PublishedExternalLinkRepository publishedExternalLinkRepository, + LinkCheckerConfigStore linkCheckerConfigStore) { + this.accessControl = accessControl; + this.linkChecker = linkChecker; + this.linkCheckerBackgroundExecutor = linkCheckerBackgroundExecutor; + this.publishedExternalLinkRepository = publishedExternalLinkRepository; + this.linkCheckerConfigStore = linkCheckerConfigStore; + } + + /** + * Check the links with the help of a background operation + * + * @return BackgroundExecution ID that can be used to track the progress + */ + public String backgroundLinkCheck() { + + if (!this.accessControl.isAllowRunLinkChecker()) { + throw new AccessDeniedException("Not allowed to run link checker"); + } + + LinkCheckerConfig config = this.linkCheckerConfigStore.get(); + + LinkCheckerExcludesPatternMatcher linkCheckerExcludesPatternMatcher = + new LinkCheckerExcludesPatternMatcher(config.getExcludes()); + + this.publishedExternalLinkRepository.cleanupUnusedLinks(); + + List linksToCheck = this.publishedExternalLinkRepository.getLinks(); + + LinkCheckerBackgroundExecution execution = + LinkCheckerBackgroundExecution.builder() + .parallel(config.getParallel()) + .topic("publisher", "link-checker") + .links(linksToCheck) + .operation( + link -> { + if (linkCheckerExcludesPatternMatcher.isExcluded(link.getUrl())) { + this.publishedExternalLinkRepository.updateCheckResult( + link.getHash(), + LinkCheckerResultItem.builder().status(StatusType.IGNORED).build()); + return; + } + LinkCheckerLink linkWithTimeout = + link.toBuilder().timeout(config.getTimeout()).build(); + LinkCheckerResultItem result = this.linkChecker.checkLink(linkWithTimeout); + this.publishedExternalLinkRepository.updateCheckResult( + linkWithTimeout.getHash(), result); + }) + .build(); + + return this.linkCheckerBackgroundExecutor.execute(execution); + } +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/GetCheckResult.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/GetCheckResult.java new file mode 100644 index 0000000..1f4076b --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/GetCheckResult.java @@ -0,0 +1,30 @@ +package com.sitepark.ies.publisher.core.linkchecker.usecase; + +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerLinkFilter; +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerResult; +import com.sitepark.ies.publisher.core.linkchecker.domain.exception.AccessDeniedException; +import com.sitepark.ies.publisher.core.linkchecker.port.AccessControl; +import com.sitepark.ies.publisher.core.linkchecker.port.PublishedExternalLinkRepository; +import jakarta.inject.Inject; + +public class GetCheckResult { + private final AccessControl accessControl; + private final PublishedExternalLinkRepository publishedExternalLinkRepository; + + @Inject + public GetCheckResult( + AccessControl accessControl, + PublishedExternalLinkRepository publishedExternalLinkRepository) { + this.accessControl = accessControl; + this.publishedExternalLinkRepository = publishedExternalLinkRepository; + } + + public LinkCheckerResult getCheckResult(LinkCheckerLinkFilter filter, int start, int limit) { + + if (!this.accessControl.isAllowGetCheckResults()) { + throw new AccessDeniedException("Not allowed to get check results"); + } + + return this.publishedExternalLinkRepository.getCheckResult(filter, start, limit); + } +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/GetCheckResultsStatistic.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/GetCheckResultsStatistic.java new file mode 100644 index 0000000..5c4a54b --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/GetCheckResultsStatistic.java @@ -0,0 +1,29 @@ +package com.sitepark.ies.publisher.core.linkchecker.usecase; + +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerResultStatistic; +import com.sitepark.ies.publisher.core.linkchecker.domain.exception.AccessDeniedException; +import com.sitepark.ies.publisher.core.linkchecker.port.AccessControl; +import com.sitepark.ies.publisher.core.linkchecker.port.PublishedExternalLinkRepository; +import jakarta.inject.Inject; + +public class GetCheckResultsStatistic { + private final AccessControl accessControl; + private final PublishedExternalLinkRepository publishedExternalLinkRepository; + + @Inject + public GetCheckResultsStatistic( + AccessControl accessControl, + PublishedExternalLinkRepository publishedExternalLinkRepository) { + this.accessControl = accessControl; + this.publishedExternalLinkRepository = publishedExternalLinkRepository; + } + + public LinkCheckerResultStatistic getCheckResultsStatistic() { + + if (!this.accessControl.isAllowGetCheckResults()) { + throw new AccessDeniedException("Not allowed to get check results statistic"); + } + + return this.publishedExternalLinkRepository.getCheckResultsStatistic(); + } +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/GetLinkCheckerConfig.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/GetLinkCheckerConfig.java new file mode 100644 index 0000000..c389a61 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/GetLinkCheckerConfig.java @@ -0,0 +1,29 @@ +package com.sitepark.ies.publisher.core.linkchecker.usecase; + +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerConfig; +import com.sitepark.ies.publisher.core.linkchecker.domain.exception.AccessDeniedException; +import com.sitepark.ies.publisher.core.linkchecker.port.AccessControl; +import com.sitepark.ies.publisher.core.linkchecker.port.LinkCheckerConfigStore; +import jakarta.inject.Inject; + +public class GetLinkCheckerConfig { + + private final AccessControl accessControl; + + private final LinkCheckerConfigStore linkCheckerConfigStore; + + @Inject + public GetLinkCheckerConfig( + AccessControl accessControl, LinkCheckerConfigStore linkCheckerConfigStore) { + this.linkCheckerConfigStore = linkCheckerConfigStore; + this.accessControl = accessControl; + } + + public LinkCheckerConfig get() { + if (!this.accessControl.isAllowGetLinkCheckerConfig()) { + throw new AccessDeniedException("Not allowed to get check results"); + } + + return this.linkCheckerConfigStore.get(); + } +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/LinkCheckByHashes.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/LinkCheckByHashes.java new file mode 100644 index 0000000..c178d04 --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/LinkCheckByHashes.java @@ -0,0 +1,95 @@ +package com.sitepark.ies.publisher.core.linkchecker.usecase; + +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerConfig; +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerLink; +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerResultItem; +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.StatusType; +import com.sitepark.ies.publisher.core.linkchecker.domain.exception.AccessDeniedException; +import com.sitepark.ies.publisher.core.linkchecker.domain.exception.LinkCheckerException; +import com.sitepark.ies.publisher.core.linkchecker.port.AccessControl; +import com.sitepark.ies.publisher.core.linkchecker.port.LinkChecker; +import com.sitepark.ies.publisher.core.linkchecker.port.LinkCheckerConfigStore; +import com.sitepark.ies.publisher.core.linkchecker.port.PublishedExternalLinkRepository; +import com.sitepark.ies.publisher.core.linkchecker.service.LinkCheckerExcludesPatternMatcher; +import jakarta.inject.Inject; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class LinkCheckByHashes { + + private final AccessControl accessControl; + private final LinkChecker linkChecker; + private final PublishedExternalLinkRepository publishedExternalLinkRepository; + private final LinkCheckerConfigStore linkCheckerConfigStore; + + @Inject + public LinkCheckByHashes( + AccessControl accessControl, + LinkChecker linkChecker, + PublishedExternalLinkRepository publishedExternalLinkRepository, + LinkCheckerConfigStore linkCheckerConfigStore) { + this.accessControl = accessControl; + this.linkChecker = linkChecker; + this.publishedExternalLinkRepository = publishedExternalLinkRepository; + this.linkCheckerConfigStore = linkCheckerConfigStore; + } + + /** Check the links whose hashes have been specified */ + public List linkCheckByHashes(Collection hashes) { + + if (!this.accessControl.isAllowRunLinkChecker()) { + throw new AccessDeniedException("Not allowed to run link checker"); + } + + LinkCheckerConfig config = this.linkCheckerConfigStore.get(); + + LinkCheckerExcludesPatternMatcher linkCheckerExcludesPatternMatcher = + new LinkCheckerExcludesPatternMatcher(config.getExcludes()); + + List linksToCheck = this.publishedExternalLinkRepository.getLinks(hashes); + + ExecutorService executorService = Executors.newFixedThreadPool(config.getParallel()); + List resultList = Collections.synchronizedList(new ArrayList<>()); + List> futures = new ArrayList<>(); + + for (LinkCheckerLink link : linksToCheck) { + CompletableFuture future = + CompletableFuture.runAsync( + () -> { + if (linkCheckerExcludesPatternMatcher.isExcluded(link.getUrl())) { + this.publishedExternalLinkRepository.updateCheckResult( + link.getHash(), + LinkCheckerResultItem.builder().status(StatusType.IGNORED).build()); + return; + } + LinkCheckerLink linkWithTimeout = + link.toBuilder().timeout(config.getTimeout()).build(); + LinkCheckerResultItem result = this.linkChecker.checkLink(linkWithTimeout); + this.publishedExternalLinkRepository.updateCheckResult( + linkWithTimeout.getHash(), result); + resultList.add( + result.toBuilder().url(link.getUrl()).hash(linkWithTimeout.getHash()).build()); + }, + executorService); + futures.add(future); + } + + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + executorService.shutdown(); + try { + if (!executorService.awaitTermination(1, TimeUnit.MINUTES)) { + throw new LinkCheckerException("Executor did not terminate in the specified time."); + } + } catch (InterruptedException e) { + throw new LinkCheckerException("Executor awaitTermination interupted.", e); + } + + return resultList; + } +} diff --git a/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/StoreLinkCheckerConfig.java b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/StoreLinkCheckerConfig.java new file mode 100644 index 0000000..402dcfa --- /dev/null +++ b/linkchecker/src/main/java/com/sitepark/ies/publisher/core/linkchecker/usecase/StoreLinkCheckerConfig.java @@ -0,0 +1,37 @@ +package com.sitepark.ies.publisher.core.linkchecker.usecase; + +import com.sitepark.ies.publisher.core.linkchecker.domain.entity.LinkCheckerConfig; +import com.sitepark.ies.publisher.core.linkchecker.domain.exception.AccessDeniedException; +import com.sitepark.ies.publisher.core.linkchecker.port.AccessControl; +import com.sitepark.ies.publisher.core.linkchecker.port.LinkCheckerConfigStore; +import com.sitepark.ies.publisher.core.linkchecker.port.LinkCheckerScheduler; +import jakarta.inject.Inject; + +public class StoreLinkCheckerConfig { + + private final AccessControl accessControl; + + private final LinkCheckerConfigStore linkCheckerConfigStore; + + private final LinkCheckerScheduler linkCheckerScheduler; + + @Inject + public StoreLinkCheckerConfig( + AccessControl accessControl, + LinkCheckerConfigStore linkCheckerConfigStore, + LinkCheckerScheduler linkCheckerScheduler) { + this.accessControl = accessControl; + this.linkCheckerConfigStore = linkCheckerConfigStore; + this.linkCheckerScheduler = linkCheckerScheduler; + } + + public void store(LinkCheckerConfig config) { + + if (!this.accessControl.isAllowGetLinkCheckerConfig()) { + throw new AccessDeniedException("Not allowed to store link checker config"); + } + + this.linkCheckerConfigStore.store(config); + this.linkCheckerScheduler.configChanged(); + } +} diff --git a/linkchecker/src/main/java/module-info.java b/linkchecker/src/main/java/module-info.java new file mode 100644 index 0000000..df49ee5 --- /dev/null +++ b/linkchecker/src/main/java/module-info.java @@ -0,0 +1,15 @@ +module com.sitepark.ies.publisher.core.linkchecker { + exports com.sitepark.ies.publisher.core.linkchecker.domain.entity; + exports com.sitepark.ies.publisher.core.linkchecker.port; + exports com.sitepark.ies.publisher.core.linkchecker.usecase; + + requires jakarta.inject; + requires org.eclipse.jdt.annotation; + requires com.github.spotbugs.annotations; + requires com.fasterxml.jackson.databind; + requires com.fasterxml.jackson.datatype.jdk8; + requires com.fasterxml.jackson.datatype.jsr310; + + opens com.sitepark.ies.publisher.core.linkchecker.domain.entity to + com.fasterxml.jackson.databind; +} diff --git a/linkchecker/src/test/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerConfigTest.java b/linkchecker/src/test/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerConfigTest.java new file mode 100644 index 0000000..a66a403 --- /dev/null +++ b/linkchecker/src/test/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerConfigTest.java @@ -0,0 +1,65 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.entity; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +class LinkCheckerConfigTest { + + @Test + @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") + void testEquals() { + EqualsVerifier.forClass(LinkCheckerConfig.class).verify(); + } + + @Test + void testDeserialize() throws JsonProcessingException { + + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new Jdk8Module()); + mapper.registerModule(new JavaTimeModule()); + + String data = + "{\"parallel\":3,\"timeout\":10,\"scheduling\":{\"type\":\"daily\",\"startTime\":\"03:00\"},\"excludes\":[{\"type\":\"REGEX\",\"pattern\":\".*\\\\.pdf\"}]}"; + + LinkCheckerConfig config = mapper.readValue(data, LinkCheckerConfig.class); + + LinkCheckerConfig expected = + LinkCheckerConfig.builder() + .parallel(3) + .timeout(10) + .scheduling(DailyScheduling.builder().startTime(3, 0).build()) + .exclude(new LinkCheckerExcludePattern(LinkCheckerExcludePatternType.REGEX, ".*\\.pdf")) + .build(); + + assertEquals(expected, config, "Unexpected config deserialization"); + } + + @Test + void testSerialize() throws JsonProcessingException { + + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new Jdk8Module()); + mapper.registerModule(new JavaTimeModule()); + + LinkCheckerConfig config = + LinkCheckerConfig.builder() + .parallel(3) + .timeout(10) + .scheduling(DailyScheduling.builder().startTime(3, 0).build()) + .exclude(new LinkCheckerExcludePattern(LinkCheckerExcludePatternType.REGEX, ".*\\.pdf")) + .build(); + + String json = mapper.writeValueAsString(config); + + assertEquals( + "{\"parallel\":3,\"timeout\":10,\"scheduling\":{\"startTime\":\"03:00\",\"type\":\"daily\"},\"excludes\":[{\"type\":\"REGEX\",\"pattern\":\".*\\\\.pdf\"}]}", + json, + "Unexpected JSON output"); + } +} diff --git a/linkchecker/src/test/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerResultItemTest.java b/linkchecker/src/test/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerResultItemTest.java new file mode 100644 index 0000000..7eb1404 --- /dev/null +++ b/linkchecker/src/test/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/LinkCheckerResultItemTest.java @@ -0,0 +1,13 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.entity; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +class LinkCheckerResultItemTest { + + @Test + @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") + void testEquals() { + EqualsVerifier.forClass(LinkCheckerResultItem.class).verify(); + } +} diff --git a/linkchecker/src/test/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/PublishedExternalLinkTest.java b/linkchecker/src/test/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/PublishedExternalLinkTest.java new file mode 100644 index 0000000..2b70135 --- /dev/null +++ b/linkchecker/src/test/java/com/sitepark/ies/publisher/core/linkchecker/domain/entity/PublishedExternalLinkTest.java @@ -0,0 +1,36 @@ +package com.sitepark.ies.publisher.core.linkchecker.domain.entity; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +class PublishedExternalLinkTest { + + @Test + @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") + void testEquals() { + EqualsVerifier.forClass(PublishedExternalLink.class).verify(); + } + + @Test + void testSetEntity() { + PublishedExternalLink link = PublishedExternalLink.builder().entity("entity").build(); + + assertEquals("entity", link.getEntity(), "unexpected entity"); + } + + @Test + void testSetSection() { + PublishedExternalLink link = PublishedExternalLink.builder().section("section").build(); + + assertEquals("section", link.getSection(), "unexpected section"); + } + + @Test + void testSetUrl() { + PublishedExternalLink link = PublishedExternalLink.builder().url("http://example.com").build(); + + assertEquals("http://example.com", link.getUrl(), "unexpected url"); + } +} diff --git a/pmd-ruleset.xml b/pmd-ruleset.xml new file mode 100644 index 0000000..ef18056 --- /dev/null +++ b/pmd-ruleset.xml @@ -0,0 +1,367 @@ + + + + Module-System Rules + + .*/target/.* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + //ClassOrInterfaceDeclaration/ClassOrInterfaceBody + [ + count(./ClassOrInterfaceBodyDeclaration/MethodDeclaration[ + not ( + starts-with(@Name,'get') + or + starts-with(@Name,'set') + or + starts-with(@Name,'is') + or + starts-with(@Name,'register') + or + starts-with(@Name,'add') + or + starts-with(@Name,'remove') + or + starts-with(@Name,'put') + or + starts-with(@Name,'has') + or + starts-with(@Name,'contains') + or + starts-with(@Name,'find') + or + starts-with(@Name,'enable') + or + starts-with(@Name,'disable') + ) + ]) > $maxmethods + ] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + //Type[../VariableDeclarator/VariableInitializer//AllocationExpression/ClassOrInterfaceType[@Image != 'ConcurrentHashMap']] +/ReferenceType/ClassOrInterfaceType[@Image = 'Map'] + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..60183af --- /dev/null +++ b/pom.xml @@ -0,0 +1,423 @@ + + + 4.0.0 + + com.sitepark.ies + ies-publisher-core + 1.0.0-SNAPSHOT + pom + IES Publisher Core + IES publisher domain und use-case implementations + https://github.com/sitepark/ies-publisher-core + + + Sitepark + https://www.sitepark.com + + + + + The MIT License + https://opensource.org/licenses/MIT + + + + + + veltrup@sitepark + Holger Veltrup + veltrup@sitepark.com + https://github.com/sitepark-veltrup + Sitepark + https://www.sitepark.com + + developer + + Europe/Berlin + + + + + linkchecker + + + + scm:git:git@github.com:sitepark/ies-publisher-core.git + scm:git:git@github.com:sitepark/ies-publisher-core.git + HEAD + https://github.com/sitepark/ies-publisher-core + + + + 5.10.2 + 4.8.6 + UTF-8 + 21 + true + + + + + + org.junit + junit-bom + ${junit.version} + pom + import + + + com.github.spotbugs + spotbugs-annotations + 4.8.5 + true + + + + jakarta.inject + jakarta.inject-api + 2.0.1.MR + + + + com.fasterxml.jackson.core + jackson-databind + 2.17.1 + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.17.0 + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + 2.17.1 + + + + + org.eclipse.jdt + org.eclipse.jdt.annotation + 2.3.0 + + + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + org.hamcrest + hamcrest-library + 2.2 + test + + + org.mockito + mockito-core + 5.12.0 + test + + + nl.jqno.equalsverifier + equalsverifier + 3.16.1 + test + + + com.jparams + to-string-verifier + 1.4.8 + test + + + + + + + + maven-compiler-plugin + 3.13.0 + + + maven-jar-plugin + 3.4.1 + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + org.apache.maven.plugins + maven-source-plugin + 3.3.0 + + all,-missing + + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.6.3 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.4 + + + sign-artifacts + + sign + + verify + + + + + maven-release-plugin + 3.0.1 + + ci(release): + @{project.version} + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.4.1 + + + io.github.thefolle + glowing-waffle + 1.2.0 + + + + + enforce-maven + + enforce + + + + + + 3.8 + + + 21 + + + + + + verify-release + + enforce + + none + + + + false + No Snapshots Allowed! + + + + true + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.11 + + + + prepare-agent + + + + jacoco-check + + check + + + + + PACKAGE + + + LINE + COVEREDRATIO + 0.0 + + + + + + + + generate-code-coverage-report + + report + + test + + + + + com.diffplug.spotless + spotless-maven-plugin + 2.43.0 + + + + + google-java-format + + + + + 1.19.2 + + true + false + + + + + + + pom.xml + + + + -1 + + + + + + + spotless-check + + check + + verify + + + + + com.github.spotbugs + spotbugs-maven-plugin + 4.8.6.5 + + Max + Low + 20 + spotbug-exclude-filter.xml + + + + + + com.github.spotbugs + spotbugs + 4.8.6 + + + + + + check + + verify + + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.21.2 + + + pmd-ruleset.xml + + true + true + 5 + true + false + + + + pmd + + check + + verify + + + + + + + + publish-release + + + + org.sonatype.central + central-publishing-maven-plugin + 0.5.0 + true + + central + true + published + + + + + + + diff --git a/spotbug-exclude-filter.xml b/spotbug-exclude-filter.xml new file mode 100644 index 0000000..e040a28 --- /dev/null +++ b/spotbug-exclude-filter.xml @@ -0,0 +1,14 @@ + + + + + + + + + +