diff --git a/.config/checkstyle/checkstyle.xml b/.config/checkstyle/checkstyle.xml index 43b5290..463a629 100644 --- a/.config/checkstyle/checkstyle.xml +++ b/.config/checkstyle/checkstyle.xml @@ -74,6 +74,11 @@ + + + + + @@ -91,7 +96,7 @@ - + @@ -123,7 +128,8 @@ - + + diff --git a/.config/pmd/java/ruleset.xml b/.config/pmd/java/ruleset.xml new file mode 100644 index 0000000..e96576b --- /dev/null +++ b/.config/pmd/java/ruleset.xml @@ -0,0 +1,1125 @@ + + + + + This ruleset checks the code for discouraged programming constructs. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Usually all cases where `StringBuilder` (or the outdated `StringBuffer`) is used are either due to confusing (legacy) logic or in situations where it may be easily 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](https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.18.1). +* 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 working with the same 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 + + + + + + + + + + + +ZipEntry name should be sanitized. +Unsanitized names may contain '..' which can result in path traversal ("ZipSlip"). + +You can suppress this warning when you properly sanitized the name. + + 4 + + + + + + + + + + + +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 properly when the underlying classes are changed. +This can result in unexpected crashes when outdated data is deserialized. + +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 + + + + + + + + + + + + +`List` allows duplicates while a `Set` does not. +A `Set` also prevents duplicates when the ORM reads multiple identical rows from the database (e.g. when using JOIN). + + 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/.config/pmd/ruleset.xml b/.config/pmd/ruleset.xml deleted file mode 100644 index 88a7b5a..0000000 --- a/.config/pmd/ruleset.xml +++ /dev/null @@ -1,197 +0,0 @@ - - - - - This ruleset checks the code for discouraged programming constructs. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.gitattributes b/.gitattributes index 9c74e42..8ac8027 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,4 +6,4 @@ # Force MVN Wrapper Linux files LF mvnw text eol=lf -.mvn/wrapper/maven-wrapper.properties text eol=lf +maven-wrapper.properties text eol=lf diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 428d939..cb72130 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -33,6 +33,15 @@ body: validations: required: true + - type: textarea + id: description + attributes: + label: Description of the problem + description: | + Describe as exactly as possible what is not working. + validations: + required: true + - type: textarea id: steps-to-reproduce attributes: @@ -47,20 +56,6 @@ body: validations: required: true - - type: textarea - id: expected-behavior - attributes: - label: Expected behavior - description: | - Tell us what you expect to happen. - - - type: textarea - id: actual-behavior - attributes: - label: Actual behavior - description: | - Tell us what happens with the steps given above. - - type: textarea id: additional-information attributes: diff --git a/.github/workflows/broken-links.yml b/.github/workflows/broken-links.yml index 16a3f37..2675c8b 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@v6 - run: mv .github/.lycheeignore .lycheeignore - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2 + uses: lycheeverse/lychee-action@a8c4c7cb88f0c7386610c35eb25108e448569cb0 # 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 01edc3a..b1a6d66 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@v6 + - 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@v5 + 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 - + - name: Check for uncommited changes run: | if [[ "$(git status --porcelain)" != "" ]]; then @@ -65,7 +69,7 @@ jobs: fi - name: Upload demo files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: demo-files-java-${{ matrix.java }} path: ${{ env.DEMO_MAVEN_MODULE }}/target/${{ env.DEMO_MAVEN_MODULE }}.jar @@ -75,21 +79,34 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event_name != 'pull_request' || !startsWith(github.head_ref, 'renovate/') }} timeout-minutes: 15 - strategy: matrix: - java: [17] + java: [21] distribution: [temurin] - steps: - - uses: actions/checkout@v4 - + - uses: actions/checkout@v6 + - 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@v5 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-mvn-checkstyle-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-mvn-checkstyle- + + - name: CheckStyle Cache + uses: actions/cache@v5 + 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 @@ -98,21 +115,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@v6 - 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@v5 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-mvn-pmd-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-mvn-pmd- + + - name: PMD Cache + uses: actions/cache@v5 + 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 @@ -122,7 +152,7 @@ jobs: - name: Upload report if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: pmd-report if-no-files-found: ignore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2cb3393..3f55399 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@v6 + - 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@v5 + 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 @@ -51,26 +61,18 @@ jobs: needs: [check-code] timeout-minutes: 10 outputs: - upload_url: ${{ steps.create_release.outputs.upload_url }} + upload_url: ${{ steps.create-release.outputs.upload_url }} steps: - - uses: actions/checkout@v4 - + - uses: actions/checkout@v6 + - 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@e5f206451d4ace2da9916d01f1aef279997f8659 # v1 + id: create-release + uses: shogo82148/actions-create-release@559c27ce7eb834825e2b55927c64f6d1bd1db716 # 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@v6 + - 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@v6 + - 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@v5 + 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@v6 + - 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..6471ce7 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@v6 with: sparse-checkout: .github/labels.yml diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index 8a85891..2d13d77 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@v6 + + - 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 647ada3..a2462d6 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@v6 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@v6 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 5c85054..eb4294a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,12 @@ # Maven target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties -# https://github.com/takari/maven-wrapper#usage-without-binary-jar + +# Maven Wrapper .mvn/wrapper/maven-wrapper.jar +# Maven Flatten Plugin +.flattened-pom.xml # Compiled class file *.class @@ -18,20 +14,12 @@ buildNumber.properties # Log file *.log -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - # Package/Binary Files don't belong into a git repo *.jar *.war -*.nar *.ear *.zip *.tar.gz -*.rar *.dll *.exe *.bin @@ -39,27 +27,11 @@ buildNumber.properties # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* -# JRebel -**/resources/rebel.xml -**/resources/rebel-remote.xml - -# eclispe stuff for root -/.settings/ -/.classpath -/.project - - -# eclispe stuff for modules -/*/.metadata/ -/*/.apt_generated_tests/ -/*/.settings/ -/*/.classpath -/*/.project -/*/RemoteSystemsTempFiles/ - -#custom -.flattened-pom.xml -.tern-project +# Eclipse +.metadata +.settings +.classpath +.project # == IntelliJ == *.iml @@ -72,6 +44,8 @@ hs_err_pid* !.idea/saveactions_settings.xml !.idea/checkstyle-idea.xml !.idea/externalDependencies.xml +!.idea/pmd-x.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..a751c41 100644 --- a/.idea/checkstyle-idea.xml +++ b/.idea/checkstyle-idea.xml @@ -1,7 +1,7 @@ - 10.21.0 + 13.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/pmd-x.xml b/.idea/pmd-x.xml new file mode 100644 index 0000000..260e454 --- /dev/null +++ b/.idea/pmd-x.xml @@ -0,0 +1,27 @@ + + + + false + true + true + SUPPORTED_ONLY_WITH_TESTS + + + + \ 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 @@