diff --git a/.config/checkstyle/checkstyle.xml b/.config/checkstyle/checkstyle.xml index 43b5290..cd40139 100644 --- a/.config/checkstyle/checkstyle.xml +++ b/.config/checkstyle/checkstyle.xml @@ -65,6 +65,7 @@ + @@ -74,11 +75,20 @@ + + + + + + + + + + + - - - + @@ -91,7 +101,7 @@ - + @@ -123,7 +133,8 @@ - + + @@ -134,6 +145,9 @@ + + + diff --git a/.config/pmd/java/ruleset.xml b/.config/pmd/java/ruleset.xml index 267fa5e..4c72bf2 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. @@ -10,11 +10,15 @@ + + + + - + @@ -26,6 +30,7 @@ + @@ -41,8 +46,10 @@ + + @@ -134,6 +141,7 @@ + @@ -142,22 +150,26 @@ - + + + + - + + @@ -184,6 +196,9 @@ + + + @@ -197,22 +212,79 @@ + + + +`Optional#get` can be interpreted as a getter by developers, however this is not the case as it throws an exception when empty. + +It should be replaced by +* doing a mapping directly using `.map` or `.ifPresent` +* using the preferred `.orElseThrow`, `.orElse` or `.or` methods + +Java Developer Brian Goetz also writes regarding this topic: + +> Java 8 was a huge improvement to the platform, but one of the few mistakes we made was the naming of `Optional.get()`, because the name just invites people to call it without calling `isPresent()`, undermining the whole point of using `Optional` in the first place. +> +> During the Java 9 time frame, we proposed to deprecate `Optional.get()`, but the public response to that was ... let's say cold. As a smaller step, we introduced `orElseThrow()` in 10 (see [JDK-8140281](https://bugs.openjdk.java.net/browse/JDK-8140281)) as a more transparently named synonym for the current pernicious behavior of `get()`. IDEs warn on unconditional use of `get()`, but not on `orElseThrow()`, which is a step forward in teaching people to code better. The question is, in a sense, a "glass half empty" view of the current situation; `get()` is still problematic. + + 3 + + + + + + + + + + + +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 setting the value. - It may also overwrite user defined options or properties. +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. +Try to pass the value only to the place where it's really needed and use it there accordingly. 3 - @@ -225,16 +297,16 @@ message="Avoid @PostConstruct" class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"> - Using a `@PostConstruct` method is usually only done when field injection is used and initialization needs to be performed after that. +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. +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 - @@ -247,15 +319,15 @@ message="Avoid @PreDestroy" class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"> - `@PreDestroy` should be replaced by implementing `AutoCloseable` and overwriting the `close` method instead. +`@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. +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 - @@ -268,16 +340,16 @@ message="Avoid unmanaged threads" class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"> - 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. +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. +Use managed Thread services like `ExecutorService` and `CompletableFuture` instead. 3 - @@ -285,23 +357,46 @@ + + +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). +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. +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. +Use proven data interchange formats like JSON instead. 2 - @@ -309,4 +404,758 @@ + + + + + +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/topo/upstream.yml b/.config/topo/upstream.yml new file mode 100644 index 0000000..0cc67cd --- /dev/null +++ b/.config/topo/upstream.yml @@ -0,0 +1,2 @@ +- url: https://github.com/xdev-software/standard-maven-template.git + branch: master diff --git a/.github/.lycheeignore b/.github/.lycheeignore index dc88a07..217b0ae 100644 --- a/.github/.lycheeignore +++ b/.github/.lycheeignore @@ -1,3 +1,4 @@ # Ignorefile for broken link check localhost mvnrepository.com +stackoverflow.com diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index a167664..b3c5aa6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -61,4 +61,4 @@ body: attributes: label: Additional information description: | - Any other relevant information you'd like to include + Any other relevant information you'd like to include, e.g. screenshots or video recordings of the bug diff --git a/.github/workflows/broken-links.yml b/.github/workflows/broken-links.yml index 5921f76..8aeed09 100644 --- a/.github/workflows/broken-links.yml +++ b/.github/workflows/broken-links.yml @@ -3,7 +3,7 @@ name: Broken links on: workflow_dispatch: schedule: - - cron: "23 23 * * 0" + - cron: "23 5 * * 0" permissions: issues: write @@ -13,20 +13,21 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - run: mv .github/.lycheeignore .lycheeignore - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@885c65f3dc543b57c898c8099f4e08c8afd178a2 # v2 + uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2 with: + args: "--verbose --no-progress './**/*.md'" 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 }} @@ -38,7 +39,7 @@ jobs: - name: Create Issue From File if: steps.lychee.outputs.exit_code != 0 - uses: peter-evans/create-issue-from-file@e8ef132d6df98ed982188e460ebb3b5d4ef3a9cd # v5 + 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 61ca4db..7e97850 100644 --- a/.github/workflows/check-build.yml +++ b/.github/workflows/check-build.yml @@ -28,24 +28,25 @@ jobs: timeout-minutes: 30 strategy: matrix: - java: [21] + java: [21, 25] distribution: [temurin] steps: - - uses: actions/checkout@v5 - - - name: Set up JDK - uses: actions/setup-java@v5 - with: - distribution: ${{ matrix.distribution }} - java-version: ${{ matrix.java }} - - - 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- + - uses: actions/checkout@v7 + + - parallel: + - name: Set up JDK + uses: actions/setup-java@v6 + with: + distribution: ${{ matrix.distribution }} + java-version: ${{ matrix.java }} + + - name: Cache Maven + uses: actions/cache@v6 + 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 @@ -69,7 +70,7 @@ jobs: fi - name: Upload demo files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: demo-files-java-${{ matrix.java }} path: ${{ env.DEMO_MAVEN_MODULE }}/target/${{ env.DEMO_MAVEN_MODULE }}.jar @@ -84,29 +85,30 @@ jobs: java: [21] distribution: [temurin] steps: - - uses: actions/checkout@v5 - - - name: Set up JDK - uses: actions/setup-java@v5 - with: - distribution: ${{ matrix.distribution }} - java-version: ${{ matrix.java }} - - - 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- + - uses: actions/checkout@v7 + + - parallel: + - name: Set up JDK + uses: actions/setup-java@v6 + with: + distribution: ${{ matrix.distribution }} + java-version: ${{ matrix.java }} + + - name: Cache Maven + uses: actions/cache@v6 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-mvn-checkstyle-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-mvn-checkstyle- + + - name: CheckStyle Cache + uses: actions/cache@v6 + 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 @@ -120,29 +122,30 @@ jobs: java: [21] distribution: [temurin] steps: - - uses: actions/checkout@v5 - - - name: Set up JDK - uses: actions/setup-java@v5 - with: - distribution: ${{ matrix.distribution }} - java-version: ${{ matrix.java }} - - - 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- + - uses: actions/checkout@v7 + + - parallel: + - name: Set up JDK + uses: actions/setup-java@v6 + with: + distribution: ${{ matrix.distribution }} + java-version: ${{ matrix.java }} + + - name: Cache Maven + uses: actions/cache@v6 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-mvn-pmd-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-mvn-pmd- + + - name: PMD Cache + uses: actions/cache@v6 + 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 @@ -151,8 +154,8 @@ jobs: run: ./mvnw -B pmd:aggregate-cpd pmd:cpd-check -P pmd -DskipTests -T2C - name: Upload report - if: always() - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + uses: actions/upload-artifact@v7 with: name: pmd-report if-no-files-found: ignore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 652ee3d..dc504ce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,22 +18,23 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v5 - - - name: Set up JDK - uses: actions/setup-java@v5 - with: - java-version: '21' - distribution: 'temurin' - - # 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- + - uses: actions/checkout@v7 + + - parallel: + - name: Set up JDK + uses: actions/setup-java@v6 + with: + java-version: '21' + distribution: 'temurin' + + # Try to reuse existing cache from check-build + - name: Try restore Maven Cache + uses: actions/cache/restore@v6 + 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 @@ -63,7 +64,7 @@ jobs: outputs: upload_url: ${{ steps.create-release.outputs.upload_url }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Configure Git run: | @@ -91,7 +92,7 @@ jobs: - name: Create Release id: create-release - uses: shogo82148/actions-create-release@7b89596097b26731bda0852f1504f813499079ee # v1 + uses: shogo82148/actions-create-release@8674c0ecee617c8dfb006526bcc22a362c70b88a # v1 with: tag_name: v${{ steps.version.outputs.release }} release_name: v${{ steps.version.outputs.release }} @@ -115,7 +116,7 @@ jobs: needs: [prepare-release] timeout-minutes: 60 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Init Git and pull run: | @@ -124,7 +125,7 @@ jobs: git pull - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: # running setup-java overwrites the settings.xml distribution: 'temurin' java-version: '21' @@ -141,7 +142,7 @@ jobs: MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: # running setup-java again overwrites the settings.xml distribution: 'temurin' java-version: '21' @@ -163,7 +164,7 @@ jobs: needs: [prepare-release] timeout-minutes: 15 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Init Git and pull run: | @@ -172,14 +173,14 @@ jobs: git pull - name: Setup - Java - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: java-version: '21' distribution: 'temurin' # Try to reuse existing cache from check-build - name: Try restore Maven Cache - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v6 with: path: ~/.m2/repository key: ${{ runner.os }}-mvn-build-${{ hashFiles('**/pom.xml') }} @@ -191,7 +192,7 @@ jobs: working-directory: ${{ env.PRIMARY_MAVEN_MODULE }} - name: Deploy to Github pages - uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4 + uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./${{ env.PRIMARY_MAVEN_MODULE }}/target/site @@ -202,7 +203,7 @@ jobs: needs: [publish-maven] timeout-minutes: 10 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Init Git and pull run: | diff --git a/.github/workflows/report-gha-workflow-security-problems.yml b/.github/workflows/report-gha-workflow-security-problems.yml new file mode 100644 index 0000000..7847028 --- /dev/null +++ b/.github/workflows/report-gha-workflow-security-problems.yml @@ -0,0 +1,61 @@ +name: Report workflow security problems + +on: + workflow_dispatch: + push: + branches: [ develop ] + paths: + - '.github/workflows/**' + +permissions: + issues: write + +jobs: + prt: + runs-on: ubuntu-latest + timeout-minutes: 15 + # Only run this in our repos (Prevent notification spam by forks) + if: ${{ github.repository_owner == 'xdev-software' }} + steps: + - uses: actions/checkout@v7 + + - name: Check + id: check + run: | + grep -l 'pull_request_target:' --exclude report-gha-workflow-security-problems.yml *.yml > reported.txt && exit 1 || exit 0 + working-directory: .github/workflows + + - name: Find already existing issue + id: find-issue + if: ${{ !cancelled() }} + run: | + echo "number=$(gh issue list -l 'bug' -l 'automated' -L 1 -S 'in:title "Incorrectly configure GHA workflow (prt)"' -s 'open' --json 'number' --jq '.[].number')" >> $GITHUB_OUTPUT + env: + GH_TOKEN: ${{ github.token }} + + - name: Close issue if everything is fine + if: ${{ success() && steps.find-issue.outputs.number != '' }} + run: gh issue close -r 'not planned' ${{ steps.find-issue.outputs.number }} + env: + GH_TOKEN: ${{ github.token }} + + - name: Create report + if: ${{ failure() && steps.check.conclusion == 'failure' }} + run: | + echo 'Detected usage of `pull_request_target`. This event is dangerous and MUST NOT BE USED AT ALL COST!' > reported.md + echo '' >> reported.md + echo '/cc @xdev-software/gha-workflow-security' >> reported.md + echo '' >> reported.md + echo '```' >> reported.md + cat .github/workflows/reported.txt >> reported.md + echo '```' >> reported.md + cat reported.md + + - name: Create Issue From File + if: ${{ failure() && steps.check.conclusion == 'failure' }} + uses: peter-evans/create-issue-from-file@fca9117c27cdc29c6c4db3b86c48e4115a786710 # v6 + with: + issue-number: ${{ steps.find-issue.outputs.number }} + title: 'Incorrectly configure GHA workflow (prt)' + content-filepath: ./reported.md + labels: bug, automated diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index f6c50a1..dc68d05 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@v5 + - uses: actions/checkout@v7 with: sparse-checkout: .github/labels.yml diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index eed206f..3f23c1e 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -11,10 +11,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: # running setup-java overwrites the settings.xml distribution: 'temurin' java-version: '21' @@ -31,7 +31,7 @@ jobs: MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} - name: Set up JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: # running setup-java again overwrites the settings.xml distribution: 'temurin' java-version: '21' diff --git a/.github/workflows/update-from-template.yml b/.github/workflows/update-from-template.yml deleted file mode 100644 index 1088171..0000000 --- a/.github/workflows/update-from-template.yml +++ /dev/null @@ -1,320 +0,0 @@ -name: Update from Template - -# This workflow keeps the repo up to date with changes from the template repo (REMOTE_URL) -# It duplicates the REMOTE_BRANCH (into UPDATE_BRANCH) and tries to merge it into -# this repos default branch (which is checked out here) -# Note that this requires a PAT (Personal Access Token) - at best from a servicing account -# PAT permissions: read:discussion, read:org, repo, workflow -# Also note that you should have at least once merged the template repo into the current repo manually -# otherwise a "refusing to merge unrelated histories" error might occur. - -on: - schedule: - - cron: '55 2 * * 1' - workflow_dispatch: - inputs: - no_automatic_merge: - type: boolean - description: 'No automatic merge' - default: false - -env: - UPDATE_BRANCH: update-from-template - UPDATE_BRANCH_MERGED: update-from-template-merged - REMOTE_URL: https://github.com/xdev-software/standard-maven-template.git - REMOTE_BRANCH: master - -permissions: - contents: write - pull-requests: write - -jobs: - update: - runs-on: ubuntu-latest - timeout-minutes: 60 - outputs: - 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@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" - git config --global user.name "XDEV Bot" - - - name: Manage branches - id: manage-branches - run: | - echo "Adding remote template-repo" - git remote add template ${{ env.REMOTE_URL }} - - echo "Fetching remote template repo" - git fetch template - - echo "Deleting local branches that will contain the updates - if present" - git branch -D ${{ env.UPDATE_BRANCH }} || true - git branch -D ${{ env.UPDATE_BRANCH_MERGED }} || true - - echo "Checking if the remote template repo has new commits" - git rev-list ..template/${{ env.REMOTE_BRANCH }} - - if [ $(git rev-list --count ..template/${{ env.REMOTE_BRANCH }}) -eq 0 ]; then - echo "There are no commits new commits on the template repo" - - echo "Deleting origin branch(es) that contain the updates - if present" - git push -f origin --delete ${{ env.UPDATE_BRANCH }} || true - git push -f origin --delete ${{ env.UPDATE_BRANCH_MERGED }} || true - - echo "create_update_branch_pr=0" >> $GITHUB_OUTPUT - echo "create_update_branch_merged_pr=0" >> $GITHUB_OUTPUT - exit 0 - fi - - echo "Found new commits on the template repo" - - echo "Creating update branch" - git branch ${{ env.UPDATE_BRANCH }} template/${{ env.REMOTE_BRANCH }} - git branch --unset-upstream ${{ env.UPDATE_BRANCH }} - - echo "Pushing update branch" - git push -f -u origin ${{ env.UPDATE_BRANCH }} - - echo "Getting base branch" - base_branch=$(git branch --show-current) - echo "Base branch is $base_branch" - echo "base_branch=$base_branch" >> $GITHUB_OUTPUT - - echo "Trying to create auto-merged branch ${{ env.UPDATE_BRANCH_MERGED }}" - git branch ${{ env.UPDATE_BRANCH_MERGED }} ${{ env.UPDATE_BRANCH }} - git checkout ${{ env.UPDATE_BRANCH_MERGED }} - - echo "Merging branch $base_branch into ${{ env.UPDATE_BRANCH_MERGED }}" - git merge $base_branch && merge_exit_code=$? || merge_exit_code=$? - if [ $merge_exit_code -ne 0 ]; then - echo "Auto merge failed! Manual merge required" - echo "::notice ::Auto merge failed - Manual merge required" - - echo "Cleaning up failed merge" - git merge --abort - git checkout $base_branch - git branch -D ${{ env.UPDATE_BRANCH_MERGED }} || true - - echo "Deleting auto-merge branch - if present" - git push -f origin --delete ${{ env.UPDATE_BRANCH_MERGED }} || true - - echo "create_update_branch_pr=1" >> $GITHUB_OUTPUT - echo "create_update_branch_merged_pr=0" >> $GITHUB_OUTPUT - exit 0 - fi - - echo "Post processing: Trying to automatically fill in template variables" - find . -type f \ - -not -path "./.git/**" \ - -not -path "./.github/workflows/update-from-template.yml" -print0 \ - | xargs -0 sed -i "s/template-placeholder/${GITHUB_REPOSITORY#*/}/g" - - git status - git add --all - - if [[ "$(git status --porcelain)" != "" ]]; then - echo "Filled in template; Committing" - - git commit -m "Fill in template" - fi - - echo "Pushing auto-merged branch" - git push -f -u origin ${{ env.UPDATE_BRANCH_MERGED }} - - echo "update_branch_merged_commit=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - - echo "Restoring base branch $base_branch" - git checkout $base_branch - - echo "create_update_branch_pr=0" >> $GITHUB_OUTPUT - echo "create_update_branch_merged_pr=1" >> $GITHUB_OUTPUT - echo "try_close_update_branch_pr=1" >> $GITHUB_OUTPUT - - - name: Create/Update PR update_branch - if: steps.manage-branches.outputs.create_update_branch_pr == 1 - env: - GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} - run: | - gh_pr_up() { - gh pr create -H "${{ env.UPDATE_BRANCH }}" "$@" || (git checkout "${{ env.UPDATE_BRANCH }}" && gh pr edit "$@") - } - gh_pr_up -B "${{ steps.manage-branches.outputs.base_branch }}" \ - --title "Update from template" \ - --body "An automated PR to sync changes from the template into this repo" - - # Ensure that only a single PR is open (otherwise confusion and spam) - - name: Close PR update_branch - if: steps.manage-branches.outputs.try_close_update_branch_pr == 1 - env: - GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} - run: | - gh pr close "${{ env.UPDATE_BRANCH }}" || true - - - name: Create/Update PR update_branch_merged - if: steps.manage-branches.outputs.create_update_branch_merged_pr == 1 - env: - GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} - run: | - gh_pr_up() { - gh pr create -H "${{ env.UPDATE_BRANCH_MERGED }}" "$@" || (git checkout "${{ env.UPDATE_BRANCH_MERGED }}" && gh pr edit "$@") - } - gh_pr_up -B "${{ steps.manage-branches.outputs.base_branch }}" \ - --title "Update from template (auto-merged)" \ - --body "An automated PR to sync changes from the template into this repo" - - # Wait a moment so that checks of PR have higher prio than following job - sleep 3 - - # Split into two jobs to help with executor starvation - auto-merge: - needs: [update] - if: needs.update.outputs.create_update_branch_merged_pr == 1 - runs-on: ubuntu-latest - timeout-minutes: 60 - steps: - - 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" - git config --global user.name "XDEV Bot" - - - name: Checking if auto-merge for PR update_branch_merged can be done - id: auto-merge-check - env: - GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} - run: | - not_failed_conclusion="skipped|neutral|success" - not_relevant_app_slug="dependabot|github-pages|sonarqubecloud" - - echo "Waiting for checks to start..." - sleep 40s - - for i in {1..20}; do - echo "Checking if PR can be auto-merged. Try: $i" - - echo "Checking if update-branch-merged exists" - git fetch - if [[ $(git ls-remote --heads origin refs/heads/${{ env.UPDATE_BRANCH_MERGED }}) ]]; then - echo "Branch still exists; Continuing..." - else - echo "Branch origin/${{ env.UPDATE_BRANCH_MERGED }} is missing" - exit 0 - fi - - echo "Fetching checks" - cs_response=$(curl -sL \ - --fail-with-body \ - --connect-timeout 60 \ - --max-time 120 \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GH_TOKEN" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/repos/${{ github.repository }}/commits/${{ needs.update.outputs.update_branch_merged_commit }}/check-suites) - - cs_data=$(echo $cs_response | jq '.check_suites[] | { conclusion: .conclusion, slug: .app.slug, check_runs_url: .check_runs_url }') - echo $cs_data - - if [[ -z "$cs_data" ]]; then - echo "No check suite data - Assuming that there are no checks to run" - - echo "perform=1" >> $GITHUB_OUTPUT - exit 0 - fi - - cs_failed=$(echo $cs_data | jq --arg x "$not_failed_conclusion" 'select ((.conclusion == null or (.conclusion | test($x))) | not)') - if [[ -z "$cs_failed" ]]; then - echo "No check failed so far; Checking if relevant checks are still running" - - cs_relevant_still_running=$(echo $cs_data | jq --arg x "$not_relevant_app_slug" 'select (.conclusion == null and (.slug | test($x) | not))') - if [[ -z $cs_relevant_still_running ]]; then - echo "All relevant checks finished - PR can be merged" - - echo "perform=1" >> $GITHUB_OUTPUT - exit 0 - else - echo "Relevant checks are still running" - echo $cs_relevant_still_running - fi - else - echo "Detected failed check" - echo $cs_failed - - echo "perform=0" >> $GITHUB_OUTPUT - exit 0 - fi - - echo "Waiting before next run..." - sleep 30s - done - - echo "Timed out - Assuming executor starvation - Forcing merge" - echo "perform=1" >> $GITHUB_OUTPUT - - - name: Auto-merge update_branch_merged - if: steps.auto-merge-check.outputs.perform == 1 - run: | - echo "Getting base branch" - base_branch=$(git branch --show-current) - echo "Base branch is $base_branch" - - echo "Fetching..." - git fetch - if [[ $(git rev-parse origin/${{ env.UPDATE_BRANCH_MERGED }}) ]]; then - echo "Branch still exists; Continuing..." - else - echo "Branch origin/${{ env.UPDATE_BRANCH_MERGED }} is missing" - exit 0 - fi - - expected_commit="${{ needs.update.outputs.update_branch_merged_commit }}" - actual_commit=$(git rev-parse origin/${{ env.UPDATE_BRANCH_MERGED }}) - if [[ "$expected_commit" != "$actual_commit" ]]; then - echo "Branch ${{ env.UPDATE_BRANCH_MERGED }} contains unexpected commit $actual_commit" - echo "Expected: $expected_commit" - - exit 0 - fi - - echo "Ensuring that current branch $base_branch is up-to-date" - git pull - - echo "Merging origin/${{ env.UPDATE_BRANCH_MERGED }} into $base_branch" - git merge origin/${{ env.UPDATE_BRANCH_MERGED }} && merge_exit_code=$? || merge_exit_code=$? - if [ $merge_exit_code -ne 0 ]; then - echo "Unexpected merge failure $merge_exit_code - Requires manual resolution" - - exit 0 - fi - - if [[ "${{ inputs.no_automatic_merge }}" == "true" ]]; then - echo "Exiting due no_automatic_merge" - - exit 0 - fi - - echo "Pushing" - git push - - echo "Cleaning up" - git branch -D ${{ env.UPDATE_BRANCH }} || true - git branch -D ${{ env.UPDATE_BRANCH_MERGED }} || true - git push -f origin --delete ${{ env.UPDATE_BRANCH }} || true - git push -f origin --delete ${{ env.UPDATE_BRANCH_MERGED }} || true diff --git a/.gitignore b/.gitignore index 464aa81..eb4294a 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ hs_err_pid* !.idea/saveactions_settings.xml !.idea/checkstyle-idea.xml !.idea/externalDependencies.xml +!.idea/pmd-x.xml !.idea/PMDPlugin.xml !.idea/inspectionProfiles/ diff --git a/.idea/PMDPlugin.xml b/.idea/PMDPlugin.xml deleted file mode 100644 index 0936e51..0000000 --- a/.idea/PMDPlugin.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml index ec555b5..b8b753e 100644 --- a/.idea/checkstyle-idea.xml +++ b/.idea/checkstyle-idea.xml @@ -1,7 +1,7 @@ - 11.0.0 + latest JavaOnlyWithTests true true diff --git a/.idea/externalDependencies.xml b/.idea/externalDependencies.xml index 78be5b8..0b477b8 100644 --- a/.idea/externalDependencies.xml +++ b/.idea/externalDependencies.xml @@ -3,5 +3,6 @@ + \ No newline at end of file diff --git a/.idea/pmd-x.xml b/.idea/pmd-x.xml new file mode 100644 index 0000000..7b3b48f --- /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/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index c0bcafe..216df05 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,3 +1,3 @@ wrapperVersion=3.3.4 distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip diff --git a/CHANGELOG.md b/CHANGELOG.md index 44fb09c..feb012c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,12 @@ -# 2.2.2 -* Re-Release due to Sonatype/Maven Central incorrectly claiming that the previous release is incorrectly signed #153 +# 2.3.0 +* Update minimum Java version to 21 -# 2.2.1 +# 2.2.3 * Re-Release due to Sonatype/Maven Central incorrectly claiming that the previous release is incorrectly signed #153 # 2.2.0 +_Not available due to validation error on Sonatype/Maven Central preventing publishing.
_ +_The problem was resolved in `2.2.3`_ * Added `computeIfAbsent` * Improved logging performance * Scheduler: Try to remove tasks immediately when cancelled to prevent unbound retention of cancelled tasks diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0e6ad0d..54d09e0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,74 +1,56 @@ -## Contributing +# Contributing -We would absolutely love to get the community involved, and we welcome any form of contributions – comments and questions on different communication channels, issues and pull request and anything that you build and share using our components. +We would absolutely love to get the community involved and we welcome any form of contributions - comments and questions, issues, pull request and anything that you build and share using our project. -### Communication channels +## Communication channels * Communication is primarily done using issues. -* If you need support as soon as possible and you can't wait for any pull request, feel free to use [our support](https://xdev.software/en/services/support). +* If you need support as soon as possible and you can't wait for any pull request, feel free to use [our support](https://xdev.software/en/services-products/support). * As a last resort measure or on otherwise important matter you may also [contact us directly](https://xdev.software/en/about-us/contact). -### Ways to help -* **Report bugs**
Create an issue or send a pull request +## Ways to help +* **Report bugs**
Create an issue and send a pull request * **Send pull requests**
If you want to contribute code, check out the development instructions below. * However when contributing larger new features, please first discuss the change you wish to make via issue with the owners of this repository before making it.
Otherwise your work might be rejected and your effort was pointless. + * Please also note that your pull request might not be noticed immediately when it's not attached to an issue. -We also encourage you to read the [contribution instructions by GitHub](https://docs.github.com/en/get-started/quickstart/contributing-to-projects). +We also encourage you to read the [contribution instructions by GitHub](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project). ## Developing -### Software Requirements -You should have the following things installed: -* Git -* Java 21 - should be as unmodified as possible (Recommended: [Eclipse Adoptium](https://adoptium.net/temurin/releases/)) -* Maven (Note that the [Maven Wrapper](https://maven.apache.org/wrapper/) is shipped with the repo) +Project specific development instructions can be found in [DEVELOPING.md](./DEVELOPING.md) -### Recommended setup -* Install ``IntelliJ`` (Community Edition is sufficient) - * Install the following plugins: - * [Save Actions](https://plugins.jetbrains.com/plugin/22113) - Provides save actions, like running the formatter or adding ``final`` to fields - * [SonarLint](https://plugins.jetbrains.com/plugin/7973-sonarlint) - CodeStyle/CodeAnalysis - * You may consider disabling telemetry in the settings under ``Tools > Sonarlint -> About`` - * [Checkstyle-IDEA](https://plugins.jetbrains.com/plugin/1065-checkstyle-idea) - CodeStyle/CodeAnalysis - * Import the project - * Ensure that everything is encoded in ``UTF-8`` - * Ensure that the JDK/Java-Version is correct +## On AI use / LLM generated content -## Releasing [![Build](https://img.shields.io/github/actions/workflow/status/xdev-software/expiring-limited-cache/release.yml?branch=master)](https://github.com/xdev-software/expiring-limited-cache/actions/workflows/release.yml) +_Our reason for the strict policy is due to [unqualified people using the technology](https://en.wikipedia.org/wiki/AI_slop)._ -Before releasing: -* Consider doing a [test-deployment](https://github.com/xdev-software/expiring-limited-cache/actions/workflows/test-deploy.yml?query=branch%3Adevelop) before actually releasing. -* Check the [changelog](CHANGELOG.md) +This policy was inspired by the AI policies of `curl`, `MESA`, `matplotlib` and `Ghostty`. -If the ``develop`` is ready for release, create a pull request to the ``master``-Branch and merge the changes +### Issues and security reports -When the release is finished do the following: -* Merge the auto-generated PR (with the incremented version number) back into the ``develop`` +If you asked an AI tool to find problems in the project, you must make sure to reveal this fact in your issue. -### Release failures +You must also double-check the findings carefully before reporting them to us to validate that the issue is indeed existing and working exactly as the AI says. AI-based tools frequently generate inaccurate or fabricated results. -There are 2 modes of release failure: -1. The remote server was e.g. down and non of the artifacts got published -2. There was a build failure during release and only parts of the artifacts got released +Further: It is rarely a good idea to copy and paste an AI generated report to the project. Those are typically too wordy and rarely to the point - in addition to the common fabricated details.
+If you actually find a problem with an AI and you have verified it yourself to be true: Write the issue/report yourself and explain the problem as you have learned it. This makes sure the AI-generated inaccuracies and invented issues are filtered out early before they waste more people's time. -In case 1 we can re-release the existing version,
in case 2 we have to release a new version when we can't get the artifacts deleted (as is the case with Maven Central) +As we take security reports seriously, we investigate each report with priority. This work is both time and energy consuming and pulls us away from doing other meaningful work. Fake and otherwise made up security problems effectively prevent us from doing real project work and make us waste time and resources. -#### How-to: Re-Releasing an existing version +We will ban and report users who submit made up fake reports. -1. Delete the release on GitHub -2. Delete the release Git tag from the repo (locally and remote!) -3. Delete the ``master``-Branch and re-create it from the ``develop`` branch (or reset it to the state before the release-workflow commits have been done) - * This requires __temporarily__ removing the branch protection - * Once this was done a new release is triggered immediately! +### Pull requests -#### How-to: Releasing a new version +When contributing content to the project, you give us permission to use it as-is and you must make sure you are allowed to distribute it to us. By submitting a change to us, you agree that the changes can and should be adopted and get redistributed under the project's license. Authors should be explicitly aware that the burden is on them to ensure no unlicensed code is submitted to the project. -1. Merge the ``master`` branch back into ``develop`` (or another temporary branch) -2. Make sure all master branch versions are prepared for a new release
e.g. if the broken release was ``1.0.0`` the version should now be at ``1.0.1-SNAPSHOT`` - the ``SNAPSHOT`` is important for the workflow! -3. Mark the broken release as broken e.g. inside the Changelog, GitHub Release page, etc.
-You can use something like this: - ``` - > [!WARNING] - > This release is broken as my cat accidentally clicked the abort button during the process - ``` -4. Merge the changes back into the ``master`` branch to trigger a new release +This is independent if AI is used or not. + +When contributing a pull request you should of course always make sure that the proposal is good quality and a best effort that follows our guidelines. A basic rule of thumb is that if someone can spot that the contribution was made with the help of AI, you have more work to do. + +We can accept code written with the help of AI into the project, but the code must still follow coding standards, be written clearly and adhere to all the normal requirements we have. + +### Further notices + +* External AI tooling (e.g. bots, agents) directly interacting with the project - without a human - are not allowed and will be banned and reported. +* For commit messages the same guidelines as described above for issues apply - fully generated messages are not allowed. +* AI assisted/generated commits can NOT use the `Co-authored-by` tag as this is reserved for humans - use `Assisted-by`/`Generated-by` instead. diff --git a/DEVELOPING.md b/DEVELOPING.md new file mode 100644 index 0000000..10d175e --- /dev/null +++ b/DEVELOPING.md @@ -0,0 +1,56 @@ +### Software Requirements +You should have the following things installed: +* Git +* Java 25 - should be as unmodified as possible (Recommended: [Eclipse Adoptium](https://adoptium.net/temurin/releases/)) +* Maven (Note that the [Maven Wrapper](https://maven.apache.org/tools/wrapper/) is shipped with the repo) + +### Recommended setup +* Install `IntelliJ` + * Recommended setup actions + * Disable not needed plugins + * Disable [telemetry](https://www.jetbrains.com/help/idea/settings-usage-statistics.html) + * Configure the available memory + * Import the project + * You will get prompted to install the required plugins + * Ensure that everything is encoded in `UTF-8` + * Ensure that the JDK/Java-Version is correct + + +## Releasing + +Before releasing: +* Consider doing a `test-deployment` before actually releasing. +* Check the [changelog](CHANGELOG.md) + +If the `develop` is ready for release, create a pull request to the `master`-Branch and merge the changes + +When the release is finished do the following: +* Merge the auto-generated PR (with the incremented version number) back into the `develop` + +### Release failures + +There are 2 modes of release failure: +1. The remote server was e.g. down and non of the artifacts got published +2. There was a build failure during release and only parts of the artifacts got released + +In case 1 we can re-release the existing version,
in case 2 we have to release a new version when we can't get the artifacts deleted (as is the case with Maven Central) + +#### How-to: Re-Releasing an existing version + +1. Delete the release on GitHub +2. Delete the release Git tag from the repo (locally and remote!) +3. Delete the ``master``-Branch and re-create it from the ``develop`` branch (or reset it to the state before the release-workflow commits have been done) + * This requires __temporarily__ removing the branch protection + * Once this was done a new release is triggered immediately! + +#### How-to: Releasing a new version + +1. Merge the ``master`` branch back into ``develop`` (or another temporary branch) +2. Make sure all master branch versions are prepared for a new release
e.g. if the broken release was ``1.0.0`` the version should now be at ``1.0.1-SNAPSHOT`` - the ``SNAPSHOT`` is important for the workflow! +3. Mark the broken release as broken e.g. inside the Changelog, GitHub Release page, etc.
+You can use something like this: + ``` + > [!WARNING] + > This release is broken as my cat accidentally clicked the abort button during the process + ``` +4. Merge the changes back into the ``master`` branch to trigger a new release diff --git a/README.md b/README.md index 8577231..9b461d9 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Use-case examples: [Installation guide for the latest release](https://github.com/xdev-software/expiring-limited-cache/releases/latest#Installation) ## Support -If you need support as soon as possible and you can't wait for any pull request, feel free to use [our support](https://xdev.software/en/services/support). +If you need support as soon as possible and you can't wait for any pull request, feel free to use [our support](https://xdev.software/en/services-products/support). ## Contributing See the [contributing guide](./CONTRIBUTING.md) for detailed instructions on how to get started with our project. diff --git a/expiring-limited-cache-demo/pom.xml b/expiring-limited-cache-demo/pom.xml index 2fe73d2..5063dee 100644 --- a/expiring-limited-cache-demo/pom.xml +++ b/expiring-limited-cache-demo/pom.xml @@ -7,11 +7,11 @@ software.xdev expiring-limited-cache-root - 2.2.4-SNAPSHOT + 2.3.0-SNAPSHOT expiring-limited-cache-demo - 2.2.4-SNAPSHOT + 2.3.0-SNAPSHOT jar @@ -20,7 +20,7 @@ - 17 + 21 ${javaVersion} UTF-8 @@ -39,7 +39,7 @@ org.slf4j slf4j-simple - 2.0.17 + 2.0.19 @@ -50,7 +50,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.14.0 + 3.16.0 ${maven.compiler.release} @@ -61,7 +61,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.7.1 + 3.8.0 diff --git a/expiring-limited-cache/pom.xml b/expiring-limited-cache/pom.xml index 94d38f2..c75d730 100644 --- a/expiring-limited-cache/pom.xml +++ b/expiring-limited-cache/pom.xml @@ -6,7 +6,7 @@ software.xdev expiring-limited-cache - 2.2.4-SNAPSHOT + 2.3.0-SNAPSHOT jar expiring-limited-cache @@ -42,7 +42,7 @@ - 17 + 21 ${javaVersion} UTF-8 @@ -53,18 +53,18 @@ org.slf4j slf4j-api - 2.0.17 + 2.0.19 org.slf4j slf4j-simple - 2.0.17 + 2.0.19 test org.junit.jupiter junit-jupiter-api - 5.13.4 + 6.1.3 test @@ -88,7 +88,7 @@ com.mycila license-maven-plugin - 5.0.0 + 5.1.2 ${project.organization.url} @@ -117,29 +117,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.14.0 - - - compile-java-17 - - compile - - - - compile-java-21 - compile - - compile - - - 21 - - ${project.basedir}/src/main/java21 - - true - - - + 3.16.0 ${maven.compiler.release} @@ -150,7 +128,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.11.3 + 3.12.0 attach-javadocs @@ -168,7 +146,7 @@ org.apache.maven.plugins maven-source-plugin - 3.3.1 + 3.4.0 attach-sources @@ -183,7 +161,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.5.4 + 3.6.0 @@ -195,7 +173,7 @@ org.codehaus.mojo flatten-maven-plugin - 1.7.2 + 1.8.0 ossrh @@ -241,7 +219,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.8.0 + 0.11.0 true sonatype-central-portal @@ -263,7 +241,7 @@ com.puppycrawl.tools checkstyle - 11.0.1 + 14.1.0 @@ -288,7 +266,7 @@ org.apache.maven.plugins maven-pmd-plugin - 3.27.0 + 3.28.0 true true @@ -301,12 +279,12 @@ net.sourceforge.pmd pmd-core - 7.17.0 + 7.27.0 net.sourceforge.pmd pmd-java - 7.17.0 + 7.27.0 diff --git a/expiring-limited-cache/src/main/java/software/xdev/caching/scheduledexecutorservice/DefaultCreator.java b/expiring-limited-cache/src/main/java/software/xdev/caching/scheduledexecutorservice/DefaultCreator.java index 5dd364c..253d3c3 100644 --- a/expiring-limited-cache/src/main/java/software/xdev/caching/scheduledexecutorservice/DefaultCreator.java +++ b/expiring-limited-cache/src/main/java/software/xdev/caching/scheduledexecutorservice/DefaultCreator.java @@ -18,22 +18,17 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.atomic.AtomicInteger; public final class DefaultCreator { public static ScheduledExecutorService create() { - final AtomicInteger counter = new AtomicInteger(0); final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool( 1, - r -> { - final Thread thread = new Thread(r); - thread.setName("ELC-Cleanup-" + counter.getAndIncrement()); - thread.setDaemon(true); - return thread; - }); + Thread.ofVirtual() + .name("ELC-Cleanup-", 0) + .factory()); if(scheduledExecutorService instanceof final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor) { scheduledThreadPoolExecutor.setRemoveOnCancelPolicy(true); diff --git a/expiring-limited-cache/src/main/java21/software/xdev/caching/scheduledexecutorservice/DefaultCreator.java b/expiring-limited-cache/src/main/java21/software/xdev/caching/scheduledexecutorservice/DefaultCreator.java deleted file mode 100644 index 253d3c3..0000000 --- a/expiring-limited-cache/src/main/java21/software/xdev/caching/scheduledexecutorservice/DefaultCreator.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright © 2024 XDEV Software (https://xdev.software) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package software.xdev.caching.scheduledexecutorservice; - -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledThreadPoolExecutor; - - -public final class DefaultCreator -{ - public static ScheduledExecutorService create() - { - final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool( - 1, - Thread.ofVirtual() - .name("ELC-Cleanup-", 0) - .factory()); - if(scheduledExecutorService instanceof final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor) - { - scheduledThreadPoolExecutor.setRemoveOnCancelPolicy(true); - } - return scheduledExecutorService; - } - - private DefaultCreator() - { - } -} diff --git a/pom.xml b/pom.xml index 649bab0..aaa1fdd 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ software.xdev expiring-limited-cache-root - 2.2.4-SNAPSHOT + 2.3.0-SNAPSHOT pom @@ -45,7 +45,7 @@ com.puppycrawl.tools checkstyle - 11.0.1 + 14.1.0 @@ -70,7 +70,7 @@ org.apache.maven.plugins maven-pmd-plugin - 3.27.0 + 3.28.0 true true @@ -83,12 +83,12 @@ net.sourceforge.pmd pmd-core - 7.17.0 + 7.27.0 net.sourceforge.pmd pmd-java - 7.17.0 + 7.27.0 diff --git a/renovate.json5 b/renovate.json5 index eeeb46c..4dc92f6 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -2,6 +2,16 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "rebaseWhen": "behind-base-branch", "packageRules": [ + { + "description": "Don't manage Java version in actions", + "matchDepTypes": [ + "uses-with" + ], + "matchDatasources": [ + "java-version" + ], + "enabled": false + }, { "description": "Ignore project internal dependencies", "packagePattern": "^software.xdev:expiring-limited-cache",