diff --git a/.config/pmd/java/ruleset.xml b/.config/pmd/java/ruleset.xml index 88a7b5a..6bf58b3 100644 --- a/.config/pmd/java/ruleset.xml +++ b/.config/pmd/java/ruleset.xml @@ -2,7 +2,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.github.io/ruleset_2_0_0.xsd"> This ruleset checks the code for discouraged programming constructs. @@ -11,7 +11,6 @@ - @@ -42,6 +41,7 @@ + @@ -138,6 +138,7 @@ + @@ -151,11 +152,13 @@ + + - + @@ -182,6 +185,9 @@ + + + @@ -194,4 +200,872 @@ + + + + + Usually all cases where `StringBuilder` (or the outdated `StringBuffer`) is used are either due to confusing (legacy) logic or may be replaced by a simpler string concatenation. + + Solution: + * Do not use `StringBuffer` because it's thread-safe and usually this is not needed + * If `StringBuilder` is only used in a simple method (like `toString`) and is effectively inlined: Use a simpler string concatenation (`"a" + x + "b"`). This will be optimized by the Java compiler internally. + * In all other cases: + * Check what is happening and if it makes ANY sense! If for example a CSV file is built here consider using a proper library instead! + * Abstract the Strings into a DTO, join them together using a collection (or `StringJoiner`) or use Java's Streaming API instead + + 3 + + + + + + + + + + + + Calling setters of java.lang.System usually indicates bad design and likely causes unexpected behavior. + For example, it may break when multiple Threads are setting the value. + It may also overwrite user defined options or properties. + + Try to pass the value only to the place where it's really needed and use it there accordingly. + + 3 + + + + + + + + + + + + Using a `@PostConstruct` method is usually only done when field injection is used and initialization needs to be performed after that. + + It's better to do this directly in the constructor with constructor injection, so that all logic will be encapsulated there. + This also makes using the bean in environments where JavaEE is not present - for example in tests - a lot easier, as forgetting to call the `@PostConstruct` method is no longer possible. + + 3 + + + + + + + + + + + + `@PreDestroy` should be replaced by implementing `AutoCloseable` and overwriting the `close` method instead. + + This also makes using the bean in environments where JavaEE is not present - for example in tests - a lot easier, as forgetting to call the `@PreDestroy` method is no much more difficult. + + 3 + + + + + + + + + + + + Trying to manually manage threads usually gets quickly out of control and may result in various problems like uncontrollable spawning of threads. + Threads can also not be cancelled properly. + + Use managed Thread services like `ExecutorService` and `CompletableFuture` instead. + + 3 + + + + + + + + + + + + Nearly every known usage of (Java) Object Deserialization has resulted in [a security vulnerability](https://cloud.google.com/blog/topics/threat-intelligence/hunting-deserialization-exploits?hl=en). + Vulnerabilities are so common that there are [dedicated projects for exploit payload generation](https://github.com/frohoff/ysoserial). + + Java Object Serialization may also fail to deserialize when the underlying classes are changed. + + Use proven data interchange formats like JSON instead. + + 2 + + + + + + + + + + + + + + Do not use native HTML! Use Vaadin layouts and components to create required structure. + If you are 100% sure that you escaped the value properly and you have no better options you can suppress this. + + 2 + + + + + + + + + + + + + + + + java.text.NumberFormat: DecimalFormat and ChoiceFormat are thread-unsafe. + + Solution: Create a new local one when needed in a method. + + 1 + + + + + + + + + + + + + + + A regular expression is compiled implicitly on every invocation. + Problem: This can be (CPU) expensive, depending on the length of the regular expression. + + Solution: Compile the regex pattern only once and assign it to a private static final Pattern field. + java.util.Pattern objects are thread-safe, so they can be shared among threads. + + 2 + + + + 5 and +(matches(@Image, '[\.\$\|\(\)\[\]\{\}\^\?\*\+\\]+'))) +or +self::VariableAccess and @Name=ancestor::ClassBody[1]/FieldDeclaration/VariableDeclarator[StringLiteral[string-length(@Image) > 5 and +(matches(@Image, '[\.\$\|\(\)\[\]\{\}\^\?\*\+\\]+'))] or not(StringLiteral)]/VariableId/@Name] +]]> + + + + + + + + + + + + The default constructor of ByteArrayOutputStream creates a 32 bytes initial capacity and for StringWriter 16 chars. + Such a small buffer as capacity usually needs several expensive expansions. + + Solution: Explicitly declared the buffer size so that an expansion is not needed in most cases. + Typically much larger than 32, e.g. 4096. + + 2 + + + + + + + + + + + + + + + The time to find element is O(n); n = the number of enum values. + This identical processing is executed for every call. + Considered problematic when `n > 3`. + + Solution: Use a static field-to-enum-value Map. Access time is O(1), provided the hashCode is well-defined. + Implement a fromString method to provide the reverse conversion by using the map. + + 3 + + + + 3]//MethodDeclaration/Block + //MethodCall[pmd-java:matchesSig('java.util.stream.Stream#findFirst()') or pmd-java:matchesSig('java.util.stream.Stream#findAny()')] + [//MethodCall[pmd-java:matchesSig('java.util.stream.Stream#of(_)') or pmd-java:matchesSig('java.util.Arrays#stream(_)')] + [ArgumentList/MethodCall[pmd-java:matchesSig('_#values()')]]] +]]> + + + + + fromString(String name) { + return Stream.of(values()).filter(v -> v.toString().equals(name)).findAny(); // bad: iterates for every call, O(n) access time + } +} + +Usage: `Fruit f = Fruit.fromString("banana");` + +// GOOD +public enum Fruit { + APPLE("apple"), + ORANGE("orange"), + BANANA("banana"), + KIWI("kiwi"); + + private static final Map nameToValue = + Stream.of(values()).collect(toMap(Object::toString, v -> v)); + private final String name; + + Fruit(String name) { this.name = name; } + @Override public String toString() { return name; } + public static Optional fromString(String name) { + return Optional.ofNullable(nameToValue.get(name)); // good, get from Map, O(1) access time + } +} +]]> + + + + + + A regular expression is compiled on every invocation. + Problem: this can be expensive, depending on the length of the regular expression. + + Solution: Usually a pattern is a literal, not dynamic and can be compiled only once. Assign it to a private static field. + java.util.Pattern objects are thread-safe so they can be shared among threads. + + 2 + + + + + + + + + + + + + + + + Recreating a DateTimeFormatter is relatively expensive. + + Solution: Java 8+ java.time.DateTimeFormatter is thread-safe and can be shared among threads. + Create the formatter from a pattern only once, to initialize a static final field. + + 2 + + + + + + + + + + + + Creating a security provider is expensive because of loading of algorithms and other classes. + Additionally, it uses synchronized which leads to lock contention when used with multiple threads. + + Solution: This only needs to happen once in the JVM lifetime, because once loaded the provider is typically available from the Security class. + Create the security provider only once: Only in case when it's not yet available from the Security class. + + 2 + + + + + + + + + + + + + + + Reflection is relatively expensive. + + Solution: Avoid reflection. Use the non-reflective, explicit way like generation by IDE. + + 2 + + + + + + + + + + + + + + + java.util.SimpleDateFormat is thread-unsafe. + The usual solution is to create a new one when needed in a method. + Creating SimpleDateFormat is relatively expensive. + + Solution: Use java.time.DateTimeFormatter. These classes are immutable, thus thread-safe and can be made static. + + 2 + + + + + + + + + + + + + + + Blocking calls, for instance remote calls, may exhaust the common pool for some time thereby blocking all other use of the common pool. + In addition, nested use of the common pool can lead to deadlock. Do not use the common pool for blocking calls. + The parallelStream() call uses the common pool. + + Solution: Use a dedicated thread pool with enough threads to get proper parallelism. + The number of threads in the common pool is equal to the number of CPUs and meant to utilize all of them. + It assumes CPU-intensive non-blocking processing of in-memory data. + + See also: [_Be Aware of ForkJoinPool#commonPool()_](https://dzone.com/articles/be-aware-of-forkjoinpoolcommonpool) + + 2 + + + + + + + + + list = new ArrayList(); + final ForkJoinPool myFjPool = new ForkJoinPool(10); + final ExecutorService myExePool = Executors.newFixedThreadPool(10); + + void bad1() { + list.parallelStream().forEach(elem -> storeDataRemoteCall(elem)); // bad + } + + void good1() { + CompletableFuture[] futures = list.stream().map(elem -> CompletableFuture.supplyAsync(() -> storeDataRemoteCall(elem), myExePool)) + .toArray(CompletableFuture[]::new); + CompletableFuture.allOf(futures).get(10, TimeUnit.MILLISECONDS)); + } + + void good2() throws ExecutionException, InterruptedException { + myFjPool.submit(() -> + list.parallelStream().forEach(elem -> storeDataRemoteCall(elem)) + ).get(); + } + + String storeDataRemoteCall(String elem) { + // do remote call, blocking. We don't use the returned value. + RestTemplate tmpl; + return ""; + } +} +]]> + + + + + + CompletableFuture.supplyAsync/runAsync is typically used for remote calls. + By default it uses the common pool. + The number of threads in the common pool is equal to the number of CPU's, which is suitable for in-memory processing. + For I/O, however, this number is typically not suitable because most time is spent waiting for the response and not in CPU. + The common pool must not be used for blocking calls. + + Solution: A separate, properly sized pool of threads (an Executor) should be used for the async calls. + + See also: [_Be Aware of ForkJoinPool#commonPool()_](https://dzone.com/articles/be-aware-of-forkjoinpoolcommonpool) + + 2 + + + + + + + + +>[] futures = accounts.stream() + .map(account -> CompletableFuture.supplyAsync(() -> isAccountBlocked(account))) // bad + .toArray(CompletableFuture[]::new); + } + + void good() { + CompletableFuture>[] futures = accounts.stream() + .map(account -> CompletableFuture.supplyAsync(() -> isAccountBlocked(account), asyncPool)) // good + .toArray(CompletableFuture[]::new); + } +} +]]> + + + + + + `take()` stalls indefinitely in case of hanging threads and consumes a thread. + + Solution: use `poll()` with a timeout value and handle the timeout. + + 2 + + + + + + + + + void collectAllCollectionReplyFromThreads(CompletionService> completionService) { + try { + Future> futureLocal = completionService.take(); // bad + Future> futuresGood = completionService.poll(3, TimeUnit.SECONDS); // good + responseCollector.addAll(futuresGood.get(10, TimeUnit.SECONDS)); // good + } catch (InterruptedException | ExecutionException e) { + LOGGER.error("Error in Thread : {}", e); + } catch (TimeoutException e) { + LOGGER.error("Timeout in Thread : {}", e); + } +} +]]> + + + + + + Stalls indefinitely in case of stalled Callable(s) and consumes threads. + + Solution: Provide a timeout to the invokeAll/invokeAny method and handle the timeout. + + 2 + + + + + + + + +> executeTasksBad(Collection> tasks, ExecutorService executor) throws Exception { + return executor.invokeAll(tasks); // bad, no timeout + } + private List> executeTasksGood(Collection> tasks, ExecutorService executor) throws Exception { + return executor.invokeAll(tasks, OUR_TIMEOUT_IN_MILLIS, TimeUnit.MILLISECONDS); // good + } +} +]]> + + + + + + Stalls indefinitely in case of hanging threads and consumes a thread. + + Solution: Provide a timeout value and handle the timeout. + + 2 + + + + + + + + + complFuture) throws Exception { + return complFuture.get(); // bad +} + +public static String good(CompletableFuture complFuture) throws Exception { + return complFuture.get(10, TimeUnit.SECONDS); // good +} +]]> + + + + + + + Apache HttpClient with its connection pool and timeouts should be setup once and then used for many requests. + It is quite expensive to create and can only provide the benefits of pooling when reused in all requests for that connection. + + Solution: Create/build HttpClient with proper connection pooling and timeouts once, and then use it for requests. + + 3 + + + + + + + + + connectBad(Object req) { + HttpEntity requestEntity = new HttpEntity<>(req); + + HttpClient httpClient = HttpClientBuilder.create().setMaxConnPerRoute(10).build(); // bad + return remoteCall(httpClient, requestEntity); + } +} +]]> + + + + + + Problem: Gson creation is relatively expensive. A JMH benchmark shows a 24x improvement reusing one instance. + + Solution: Since Gson objects are thread-safe after creation, they can be shared between threads. + So reuse created instances from a static field. + Pay attention to use thread-safe (custom) adapters and serializers. + + 3 + + + + + + + + + + + diff --git a/.github/workflows/broken-links.yml b/.github/workflows/broken-links.yml index 16a3f37..3a1009c 100644 --- a/.github/workflows/broken-links.yml +++ b/.github/workflows/broken-links.yml @@ -13,32 +13,32 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: mv .github/.lycheeignore .lycheeignore - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2 + uses: lycheeverse/lychee-action@885c65f3dc543b57c898c8099f4e08c8afd178a2 # v2 with: fail: false # Don't fail on broken links, create an issue instead - name: Find already existing issue id: find-issue run: | - echo "number=$(gh issue list -l 'bug' -l 'automated' -L 1 -S 'in:title \"Link Checker Report\"' -s 'open' --json 'number' --jq '.[].number')" >> $GITHUB_OUTPUT + echo "number=$(gh issue list -l 'bug' -l 'automated' -L 1 -S 'in:title "Link Checker Report"' -s 'open' --json 'number' --jq '.[].number')" >> $GITHUB_OUTPUT env: GH_TOKEN: ${{ github.token }} - + - name: Close issue if everything is fine - if: env.lychee_exit_code == 0 && steps.find-issue.outputs.number != '' + if: steps.lychee.outputs.exit_code == 0 && steps.find-issue.outputs.number != '' run: gh issue close -r 'not planned' ${{ steps.find-issue.outputs.number }} env: GH_TOKEN: ${{ github.token }} - name: Create Issue From File - if: env.lychee_exit_code != 0 - uses: peter-evans/create-issue-from-file@e8ef132d6df98ed982188e460ebb3b5d4ef3a9cd # v5 + if: steps.lychee.outputs.exit_code != 0 + uses: peter-evans/create-issue-from-file@fca9117c27cdc29c6c4db3b86c48e4115a786710 # v6 with: issue-number: ${{ steps.find-issue.outputs.number }} title: Link Checker Report diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml index 81908f0..517ee4f 100644 --- a/.github/workflows/check-build.yml +++ b/.github/workflows/check-build.yml @@ -20,32 +20,36 @@ on: - 'assets/**' env: - PRIMARY_MAVEN_MODULE: ${{ github.event.repository.name }} DEMO_MAVEN_MODULE: ${{ github.event.repository.name }}-demo jobs: build: runs-on: ubuntu-latest timeout-minutes: 30 - strategy: matrix: - java: [17, 21] + java: [17, 21, 25] distribution: [temurin] - steps: - - uses: actions/checkout@v4 - + - uses: actions/checkout@v5 + - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: ${{ matrix.distribution }} java-version: ${{ matrix.java }} - cache: 'maven' - + + - name: Cache Maven + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-mvn-build-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-mvn-build- + - name: Build with Maven run: ./mvnw -B clean package -P run-integration-tests - + - name: Check for uncommited changes run: | if [[ "$(git status --porcelain)" != "" ]]; then @@ -68,21 +72,34 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event_name != 'pull_request' || !startsWith(github.head_ref, 'renovate/') }} timeout-minutes: 15 - strategy: matrix: java: [17] distribution: [temurin] - steps: - - uses: actions/checkout@v4 - + - uses: actions/checkout@v5 + - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: ${{ matrix.distribution }} java-version: ${{ matrix.java }} - cache: 'maven' + + - name: Cache Maven + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-mvn-checkstyle-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-mvn-checkstyle- + + - name: CheckStyle Cache + uses: actions/cache@v4 + with: + path: '**/target/checkstyle-cachefile' + key: ${{ runner.os }}-checkstyle-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-checkstyle- - name: Run Checkstyle run: ./mvnw -B checkstyle:check -P checkstyle -T2C @@ -91,21 +108,34 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event_name != 'pull_request' || !startsWith(github.head_ref, 'renovate/') }} timeout-minutes: 15 - strategy: matrix: java: [17] distribution: [temurin] - steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: ${{ matrix.distribution }} java-version: ${{ matrix.java }} - cache: 'maven' + + - name: Cache Maven + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-mvn-pmd-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-mvn-pmd- + + - name: PMD Cache + uses: actions/cache@v4 + with: + path: '**/target/pmd/pmd.cache' + key: ${{ runner.os }}-pmd-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-pmd- - name: Run PMD run: ./mvnw -B test pmd:aggregate-pmd-no-fork pmd:check -P pmd -DskipTests -T2C diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c28f949..6101566 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,20 +11,30 @@ permissions: contents: write pull-requests: write +# DO NOT RESTORE CACHE for critical release steps to prevent a (extremely unlikely) scenario +# where a supply chain attack could be achieved due to poisoned cache jobs: check-code: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v4 - + - uses: actions/checkout@v5 + - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' - cache: 'maven' - + + # Try to reuse existing cache from check-build + - name: Try restore Maven Cache + uses: actions/cache/restore@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-mvn-build-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-mvn-build- + - name: Build with Maven run: ./mvnw -B clean package -T2C @@ -53,24 +63,16 @@ jobs: outputs: upload_url: ${{ steps.create-release.outputs.upload_url }} steps: - - uses: actions/checkout@v4 - + - uses: actions/checkout@v5 + - name: Configure Git run: | git config --global user.email "actions@github.com" git config --global user.name "GitHub Actions" - + - name: Un-SNAP - run: | - mvnwPath=$(readlink -f ./mvnw) - modules=("") # root - modules+=($(grep -oP '(?<=)[^<]+' 'pom.xml')) - for i in "${modules[@]}" - do - echo "Processing $i/pom.xml" - (cd "$i" && $mvnwPath -B versions:set -DremoveSnapshot -DgenerateBackupPoms=false) - done - + run: ./mvnw -B versions:set -DremoveSnapshot -DprocessAllModules -DgenerateBackupPoms=false + - name: Get version id: version run: | @@ -78,7 +80,7 @@ jobs: echo "release=$version" >> $GITHUB_OUTPUT echo "releasenumber=${version//[!0-9]/}" >> $GITHUB_OUTPUT working-directory: ${{ env.PRIMARY_MAVEN_MODULE }} - + - name: Commit and Push run: | git add -A @@ -86,10 +88,10 @@ jobs: git push origin git tag v${{ steps.version.outputs.release }} git push origin --tags - + - name: Create Release id: create-release - uses: shogo82148/actions-create-release@4661dc54f7b4b564074e9fbf73884d960de569a3 # v1 + uses: shogo82148/actions-create-release@7b89596097b26731bda0852f1504f813499079ee # v1 with: tag_name: v${{ steps.version.outputs.release }} release_name: v${{ steps.version.outputs.release }} @@ -113,27 +115,43 @@ jobs: needs: [prepare-release] timeout-minutes: 60 steps: - - uses: actions/checkout@v4 - + - uses: actions/checkout@v5 + - name: Init Git and pull run: | git config --global user.email "actions@github.com" git config --global user.name "GitHub Actions" git pull - + - name: Set up JDK - uses: actions/setup-java@v4 - with: # running setup-java again overwrites the settings.xml + uses: actions/setup-java@v5 + with: # running setup-java overwrites the settings.xml + distribution: 'temurin' java-version: '17' + server-id: github-central + server-password: PACKAGES_CENTRAL_TOKEN + gpg-passphrase: MAVEN_GPG_PASSPHRASE + gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Only import once + + - name: Publish to GitHub Packages Central + run: ../mvnw -B deploy -P publish -DskipTests -DaltDeploymentRepository=github-central::https://maven.pkg.github.com/xdev-software/central + working-directory: ${{ env.PRIMARY_MAVEN_MODULE }} + env: + PACKAGES_CENTRAL_TOKEN: ${{ secrets.PACKAGES_CENTRAL_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + + - name: Set up JDK + uses: actions/setup-java@v5 + with: # running setup-java again overwrites the settings.xml distribution: 'temurin' + java-version: '17' server-id: sonatype-central-portal server-username: MAVEN_CENTRAL_USERNAME server-password: MAVEN_CENTRAL_TOKEN gpg-passphrase: MAVEN_GPG_PASSPHRASE - gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} - name: Publish to Central Portal - run: ../mvnw -B deploy -P publish-sonatype-central-portal -DskipTests + run: ../mvnw -B deploy -P publish,publish-sonatype-central-portal -DskipTests env: MAVEN_CENTRAL_USERNAME: ${{ secrets.SONATYPE_MAVEN_CENTRAL_PORTAL_USERNAME }} MAVEN_CENTRAL_TOKEN: ${{ secrets.SONATYPE_MAVEN_CENTRAL_PORTAL_TOKEN }} @@ -145,8 +163,8 @@ jobs: needs: [prepare-release] timeout-minutes: 15 steps: - - uses: actions/checkout@v4 - + - uses: actions/checkout@v5 + - name: Init Git and pull run: | git config --global user.email "actions@github.com" @@ -154,11 +172,19 @@ jobs: git pull - name: Setup - Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' - cache: 'maven' + + # Try to reuse existing cache from check-build + - name: Try restore Maven Cache + uses: actions/cache/restore@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-mvn-build-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-mvn-build- - name: Build site run: ../mvnw -B compile site -DskipTests -T2C @@ -176,8 +202,8 @@ jobs: needs: [publish-maven] timeout-minutes: 10 steps: - - uses: actions/checkout@v4 - + - uses: actions/checkout@v5 + - name: Init Git and pull run: | git config --global user.email "actions@github.com" @@ -185,22 +211,14 @@ jobs: git pull - name: Inc Version and SNAP - run: | - mvnwPath=$(readlink -f ./mvnw) - modules=("") # root - modules+=($(grep -oP '(?<=)[^<]+' 'pom.xml')) - for i in "${modules[@]}" - do - echo "Processing $i/pom.xml" - (cd "$i" && $mvnwPath -B build-helper:parse-version versions:set -DnewVersion=\${parsedVersion.majorVersion}.\${parsedVersion.minorVersion}.\${parsedVersion.nextIncrementalVersion} -DgenerateBackupPoms=false -DnextSnapshot=true -DupdateMatchingVersions=false) - done + run: ./mvnw -B versions:set -DnextSnapshot -DprocessAllModules -DgenerateBackupPoms=false - name: Git Commit and Push run: | git add -A git commit -m "Preparing for next development iteration" git push origin - + - name: pull-request env: GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml deleted file mode 100644 index df6dbb7..0000000 --- a/.github/workflows/sonar.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: Sonar - -on: - workflow_dispatch: - push: - branches: [ develop ] - paths-ignore: - - '**.md' - - '.config/**' - - '.github/**' - - '.idea/**' - - 'assets/**' - pull_request: - branches: [ develop ] - paths-ignore: - - '**.md' - - '.config/**' - - '.github/**' - - '.idea/**' - - 'assets/**' - -env: - SONARCLOUD_ORG: ${{ github.event.organization.login }} - SONARCLOUD_HOST: https://sonarcloud.io - -jobs: - token-check: - runs-on: ubuntu-latest - if: ${{ !(github.event_name == 'pull_request' && startsWith(github.head_ref, 'renovate/')) }} - timeout-minutes: 5 - outputs: - hasToken: ${{ steps.check-token.outputs.has }} - steps: - - id: check-token - run: | - [ -z $SONAR_TOKEN ] && echo "has=false" || echo "has=true" >> "$GITHUB_OUTPUT" - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - - sonar-scan: - runs-on: ubuntu-latest - needs: token-check - if: ${{ needs.token-check.outputs.hasToken }} - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - - name: Set up JDK - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: 17 - - - name: Cache SonarCloud packages - uses: actions/cache@v4 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - - name: Cache Maven packages - uses: actions/cache@v4 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 - - - name: Build with Maven - run: | - ./mvnw -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \ - -DskipTests \ - -Dsonar.projectKey=${{ env.SONARCLOUD_ORG }}_${{ github.event.repository.name }} \ - -Dsonar.organization=${{ env.SONARCLOUD_ORG }} \ - -Dsonar.host.url=${{ env.SONARCLOUD_HOST }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index dc67287..f6c50a1 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: sparse-checkout: .github/labels.yml diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index 8a85891..b75a246 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -11,10 +11,27 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v4 - + - uses: actions/checkout@v5 + + - name: Set up JDK + uses: actions/setup-java@v5 + with: # running setup-java overwrites the settings.xml + distribution: 'temurin' + java-version: '17' + server-id: github-central + server-password: PACKAGES_CENTRAL_TOKEN + gpg-passphrase: MAVEN_GPG_PASSPHRASE + gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Only import once + + - name: Publish to GitHub Packages Central + run: ../mvnw -B deploy -P publish -DskipTests -DaltDeploymentRepository=github-central::https://maven.pkg.github.com/xdev-software/central + working-directory: ${{ env.PRIMARY_MAVEN_MODULE }} + env: + PACKAGES_CENTRAL_TOKEN: ${{ secrets.PACKAGES_CENTRAL_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + - name: Set up JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: # running setup-java again overwrites the settings.xml distribution: 'temurin' java-version: '17' @@ -22,10 +39,9 @@ jobs: server-username: MAVEN_CENTRAL_USERNAME server-password: MAVEN_CENTRAL_TOKEN gpg-passphrase: MAVEN_GPG_PASSPHRASE - gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} - name: Publish to Central Portal - run: ../mvnw -B deploy -P publish-sonatype-central-portal -DskipTests + run: ../mvnw -B deploy -P publish,publish-sonatype-central-portal -DskipTests working-directory: ${{ env.PRIMARY_MAVEN_MODULE }} env: MAVEN_CENTRAL_USERNAME: ${{ secrets.SONATYPE_MAVEN_CENTRAL_PORTAL_USERNAME }} diff --git a/.github/workflows/update-from-template.yml b/.github/workflows/update-from-template.yml index 65f56b0..1088171 100644 --- a/.github/workflows/update-from-template.yml +++ b/.github/workflows/update-from-template.yml @@ -36,14 +36,14 @@ jobs: update_branch_merged_commit: ${{ steps.manage-branches.outputs.update_branch_merged_commit }} create_update_branch_merged_pr: ${{ steps.manage-branches.outputs.create_update_branch_merged_pr }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: # Required because otherwise there are always changes detected when executing diff/rev-list fetch-depth: 0 # If no PAT is used the following error occurs on a push: # refusing to allow a GitHub App to create or update workflow `.github/workflows/xxx.yml` without `workflows` permission token: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} - + - name: Init Git run: | git config --global user.email "111048771+xdev-gh-bot@users.noreply.github.com" @@ -183,14 +183,14 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: # Required because otherwise there are always changes detected when executing diff/rev-list fetch-depth: 0 # If no PAT is used the following error occurs on a push: # refusing to allow a GitHub App to create or update workflow `.github/workflows/xxx.yml` without `workflows` permission token: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} - + - name: Init Git run: | git config --global user.email "111048771+xdev-gh-bot@users.noreply.github.com" diff --git a/.gitignore b/.gitignore index 14a1fb4..464aa81 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ hs_err_pid* !.idea/saveactions_settings.xml !.idea/checkstyle-idea.xml !.idea/externalDependencies.xml +!.idea/PMDPlugin.xml !.idea/inspectionProfiles/ .idea/inspectionProfiles/* diff --git a/.idea/PMDPlugin.xml b/.idea/PMDPlugin.xml new file mode 100644 index 0000000..0936e51 --- /dev/null +++ b/.idea/PMDPlugin.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml index b52c3e2..ec555b5 100644 --- a/.idea/checkstyle-idea.xml +++ b/.idea/checkstyle-idea.xml @@ -1,7 +1,7 @@ - 10.21.0 + 11.0.0 JavaOnlyWithTests true true diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 19681fa..21e0aff 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -96,4 +96,4 @@ - + \ No newline at end of file diff --git a/.idea/saveactions_settings.xml b/.idea/saveactions_settings.xml index 848c311..12a4f04 100644 --- a/.idea/saveactions_settings.xml +++ b/.idea/saveactions_settings.xml @@ -5,6 +5,7 @@