From a5bb2150f55b45b01499f3c7cd4945e6b9c12b90 Mon Sep 17 00:00:00 2001 From: Milan Mladoniczky <6153201+tuplle@users.noreply.github.com> Date: Mon, 9 Mar 2026 14:34:48 +0100 Subject: [PATCH 1/6] Added `ApplicationShutdownProvider` to enable graceful application shutdown. - Add `existsById` method and improve task resolution - Introduced a new `existsById` method in `IUserService` to check user existence. - Modified `resolveUserRef` in `TaskService` to return a list of resolved tasks instead of void. - Ensured consistency in validations by replacing `size()` checks with `isEmpty()`. --- .../MigrationOrderedCommandLineRunner.groovy | 20 ++++++++++-- .../engine/startup/RunnerController.groovy | 2 +- .../auth/service/AbstractUserService.java | 5 +++ .../auth/service/interfaces/IUserService.java | 2 ++ .../ApplicationShutdownProvider.java | 31 +++++++++++++++++++ .../engine/workflow/service/TaskService.java | 13 +++----- .../workflow/service/WorkflowService.java | 4 +-- .../service/interfaces/ITaskService.java | 5 ++- 8 files changed, 66 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/netgrif/application/engine/configuration/ApplicationShutdownProvider.java diff --git a/src/main/groovy/com/netgrif/application/engine/migration/MigrationOrderedCommandLineRunner.groovy b/src/main/groovy/com/netgrif/application/engine/migration/MigrationOrderedCommandLineRunner.groovy index 8fc940ac528..ff4044873d3 100644 --- a/src/main/groovy/com/netgrif/application/engine/migration/MigrationOrderedCommandLineRunner.groovy +++ b/src/main/groovy/com/netgrif/application/engine/migration/MigrationOrderedCommandLineRunner.groovy @@ -1,6 +1,6 @@ package com.netgrif.application.engine.migration - +import com.netgrif.application.engine.configuration.ApplicationShutdownProvider import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService import com.netgrif.application.engine.startup.AbstractOrderedCommandLineRunner import org.slf4j.Logger @@ -14,6 +14,7 @@ abstract class MigrationOrderedCommandLineRunner extends AbstractOrderedCommandL private static final Logger log = LoggerFactory.getLogger(MigrationOrderedCommandLineRunner) private String title = this.class.simpleName + private boolean shutdownAfterFinish = false @Autowired private MigrationRepository repository @@ -21,6 +22,9 @@ abstract class MigrationOrderedCommandLineRunner extends AbstractOrderedCommandL @Autowired private IPetriNetService service + @Autowired + private ApplicationShutdownProvider shutdownProvider + @Override void run(String... strings) throws Exception { if (repository.existsByTitle(title)) { @@ -33,7 +37,19 @@ abstract class MigrationOrderedCommandLineRunner extends AbstractOrderedCommandL repository.save(new Migration(title)) service.evictAllCaches() log.info("Migration ${title} applied") + if (shutdownAfterFinish) { + sleep(100) + shutdownProvider.shutdown(this.class) + } + } + + protected enableShutdownAfterFinish() { + this.shutdownAfterFinish = true; + } + + protected disableShutdownAfterFinish() { + this.shutdownAfterFinish = false; } abstract void migrate() -} \ No newline at end of file +} diff --git a/src/main/groovy/com/netgrif/application/engine/startup/RunnerController.groovy b/src/main/groovy/com/netgrif/application/engine/startup/RunnerController.groovy index f02d30e1c8f..d653c4c4d30 100644 --- a/src/main/groovy/com/netgrif/application/engine/startup/RunnerController.groovy +++ b/src/main/groovy/com/netgrif/application/engine/startup/RunnerController.groovy @@ -43,4 +43,4 @@ class RunnerController { } return runnerOrder } -} \ No newline at end of file +} diff --git a/src/main/java/com/netgrif/application/engine/auth/service/AbstractUserService.java b/src/main/java/com/netgrif/application/engine/auth/service/AbstractUserService.java index 2a3be9d5ed7..d9e68491cf2 100644 --- a/src/main/java/com/netgrif/application/engine/auth/service/AbstractUserService.java +++ b/src/main/java/com/netgrif/application/engine/auth/service/AbstractUserService.java @@ -53,6 +53,11 @@ public void addDefaultAuthorities(IUser user) { } } + @Override + public boolean existsById(String id) { + return repository.existsById(id); + } + @Override public IUser assignAuthority(String userId, String authorityId) { IUser user = resolveById(userId, true); diff --git a/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IUserService.java b/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IUserService.java index 01956bc2b8b..989062f7585 100644 --- a/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IUserService.java +++ b/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IUserService.java @@ -49,6 +49,8 @@ public interface IUserService { void addDefaultAuthorities(IUser user); + boolean existsById(String id); + IUser assignAuthority(String userId, String authorityId); IUser getLoggedOrSystem(); diff --git a/src/main/java/com/netgrif/application/engine/configuration/ApplicationShutdownProvider.java b/src/main/java/com/netgrif/application/engine/configuration/ApplicationShutdownProvider.java new file mode 100644 index 00000000000..57c0c18a738 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/configuration/ApplicationShutdownProvider.java @@ -0,0 +1,31 @@ +package com.netgrif.application.engine.configuration; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ApplicationShutdownProvider { + + private final ApplicationContext applicationContext; + + public void shutdown(Class calledBy, int exitCode) { + String className = calledBy == null ? "unknown" : calledBy.getSimpleName(); + log.info("Application was signalled by {} to shutdown; exit code: {}", className, exitCode); + int ec = SpringApplication.exit(applicationContext, () -> exitCode); + System.exit(ec); + } + + public void shutdown(Class calledBy) { + shutdown(calledBy, 0); + } + + public void shutdown() { + shutdown(null, 0); + } + +} diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java b/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java index 58c7db6f9d9..910e7590002 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/TaskService.java @@ -772,12 +772,9 @@ public List save(List tasks) { } @Override - public void resolveUserRef(Case useCase) { - useCase.getTasks().forEach(taskPair -> { - Optional taskOptional = taskRepository.findById(taskPair.getTask()); - taskOptional.ifPresent(task -> resolveUserRef(task, useCase)); - }); - + public List resolveUserRef(Case useCase) { + List tasks = taskRepository.findAllBy_idIn(useCase.getTasks().stream().map(TaskPair::getTask).collect(Collectors.toList())); + return tasks.stream().map(task -> resolveUserRef(task, useCase)).collect(Collectors.toList()); } @Override @@ -786,9 +783,9 @@ public Task resolveUserRef(Task task, Case useCase) { task.getNegativeViewUsers().clear(); task.getUserRefs().forEach((id, permission) -> { List userIds = getExistingUsers((UserListFieldValue) useCase.getDataSet().get(id).getValue()); - if (userIds != null && userIds.size() != 0 && permission.containsKey("view") && !permission.get("view")) { + if (userIds != null && !userIds.isEmpty() && permission.containsKey("view") && !permission.get("view")) { task.getNegativeViewUsers().addAll(userIds); - } else if (userIds != null && userIds.size() != 0) { + } else if (userIds != null && !userIds.isEmpty()) { task.addUsers(new HashSet<>(userIds), permission); } }); diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java b/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java index 3d9ac2dd2b3..2cae7ed9203 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java @@ -228,7 +228,7 @@ public Case resolveUserRef(Case useCase) { private void resolveUserRefPermissions(Case useCase, String userListId, Map permission) { List userIds = getExistingUsers((UserListFieldValue) useCase.getDataSet().get(userListId).getValue()); - if (userIds != null && userIds.size() != 0) { + if (userIds != null && !userIds.isEmpty()) { if (permission.containsKey("view") && !permission.get("view")) { useCase.getNegativeViewUsers().addAll(userIds); } else { @@ -241,7 +241,7 @@ private List getExistingUsers(UserListFieldValue userListValue) { if (userListValue == null) return null; return userListValue.getUserValues().stream().map(UserFieldValue::getId) - .filter(id -> userService.resolveById(id, false) != null) + .filter(id -> userService.existsById(id)) .collect(Collectors.toList()); } diff --git a/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/ITaskService.java b/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/ITaskService.java index f7b867eb79c..691b2de4a9b 100644 --- a/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/ITaskService.java +++ b/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/ITaskService.java @@ -15,7 +15,6 @@ import com.netgrif.application.engine.workflow.web.responsebodies.TaskReference; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Locale; @@ -106,7 +105,7 @@ public interface ITaskService { DelegateTaskEventOutcome delegateTask(LoggedUser loggedUser, String delegatedId, String taskId, Map params) throws TransitionNotExecutableException; - void resolveUserRef(Case useCase); + List resolveUserRef(Case useCase); Task resolveUserRef(Task task, Case useCase); @@ -127,4 +126,4 @@ public interface ITaskService { List save(List tasks); SetDataEventOutcome getMainOutcome(Map outcomes, String taskId); -} \ No newline at end of file +} From 938d94c1f2aa86f20c75ceb3296087d97acf6070 Mon Sep 17 00:00:00 2001 From: Milan Mladoniczky <6153201+tuplle@users.noreply.github.com> Date: Mon, 9 Mar 2026 14:55:12 +0100 Subject: [PATCH 2/6] Add `MigrationProperties` to configure migration behavior - Introduced `MigrationProperties` class to define configurable properties for migration, including skip list, cache eviction, and shutdown control. - Enhanced `MigrationOrderedCommandLineRunner` to respect `MigrationProperties` settings. - Improved logging for migration operations with additional conditions. --- .../MigrationOrderedCommandLineRunner.groovy | 15 ++++++- .../properties/MigrationProperties.java | 39 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/netgrif/application/engine/configuration/properties/MigrationProperties.java diff --git a/src/main/groovy/com/netgrif/application/engine/migration/MigrationOrderedCommandLineRunner.groovy b/src/main/groovy/com/netgrif/application/engine/migration/MigrationOrderedCommandLineRunner.groovy index ff4044873d3..a3f2a0b7b98 100644 --- a/src/main/groovy/com/netgrif/application/engine/migration/MigrationOrderedCommandLineRunner.groovy +++ b/src/main/groovy/com/netgrif/application/engine/migration/MigrationOrderedCommandLineRunner.groovy @@ -1,6 +1,7 @@ package com.netgrif.application.engine.migration import com.netgrif.application.engine.configuration.ApplicationShutdownProvider +import com.netgrif.application.engine.configuration.properties.MigrationProperties import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService import com.netgrif.application.engine.startup.AbstractOrderedCommandLineRunner import org.slf4j.Logger @@ -19,6 +20,9 @@ abstract class MigrationOrderedCommandLineRunner extends AbstractOrderedCommandL @Autowired private MigrationRepository repository + @Autowired + private MigrationProperties migrationProperties + @Autowired private IPetriNetService service @@ -31,13 +35,20 @@ abstract class MigrationOrderedCommandLineRunner extends AbstractOrderedCommandL log.info("Migration ${title} was already applied") return } + if (migrationProperties.getSkip().contains(title)) { + log.info("Migration ${title} is skipped according to property nae.migration.skip") + return + } log.info("Applying migration ${title}") migrate() repository.save(new Migration(title)) - service.evictAllCaches() + if (migrationProperties.isEvictCaches()) { + log.info("Evicting all caches after migration") + service.evictAllCaches() + } log.info("Migration ${title} applied") - if (shutdownAfterFinish) { + if (shutdownAfterFinish || migrationProperties.isShutdownAfterMigration()) { sleep(100) shutdownProvider.shutdown(this.class) } diff --git a/src/main/java/com/netgrif/application/engine/configuration/properties/MigrationProperties.java b/src/main/java/com/netgrif/application/engine/configuration/properties/MigrationProperties.java new file mode 100644 index 00000000000..aeba1845e06 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/configuration/properties/MigrationProperties.java @@ -0,0 +1,39 @@ +package com.netgrif.application.engine.configuration.properties; + + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.LinkedHashSet; +import java.util.Set; + +@Data +@Configuration +@ConfigurationProperties(prefix = "nae.migration") +public class MigrationProperties { + + /** + * A list of migration process identifiers or names that should be skipped when applying migration logic. + * This property allows you to configure specific migrations that should be ignored, + * typically useful for excluding unnecessary or problematic migrations. + */ + private Set skip = new LinkedHashSet<>(); + + /** + * Indicates whether caches should be evicted as part of the migration process. + * This property allows enabling or disabling the cache eviction mechanism, which + * is useful in ensuring consistency and up-to-date data during migration operations. + * Default value is {@code true}. + */ + private boolean evictCaches = true; + + /** + * Specifies whether the application should automatically shut down once the migration process is completed. + * This property can be used to terminate the application after the migration, ensuring a clean exit + * if no further operations are intended post-migration. + * Default value is {@code false}. + */ + private boolean shutdownAfterMigration = false; + +} From 815e2f6cb31b41d8430ccd07ff716ff2b43b22e4 Mon Sep 17 00:00:00 2001 From: Milan Mladoniczky <6153201+tuplle@users.noreply.github.com> Date: Mon, 9 Mar 2026 23:07:15 +0100 Subject: [PATCH 3/6] Increased `sleep` duration to allow elastic executor and related processes to flush work after migration. --- .../migration/MigrationOrderedCommandLineRunner.groovy | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/groovy/com/netgrif/application/engine/migration/MigrationOrderedCommandLineRunner.groovy b/src/main/groovy/com/netgrif/application/engine/migration/MigrationOrderedCommandLineRunner.groovy index a3f2a0b7b98..7c0c52d9bc1 100644 --- a/src/main/groovy/com/netgrif/application/engine/migration/MigrationOrderedCommandLineRunner.groovy +++ b/src/main/groovy/com/netgrif/application/engine/migration/MigrationOrderedCommandLineRunner.groovy @@ -49,18 +49,21 @@ abstract class MigrationOrderedCommandLineRunner extends AbstractOrderedCommandL } log.info("Migration ${title} applied") if (shutdownAfterFinish || migrationProperties.isShutdownAfterMigration()) { - sleep(100) + // sleep is for elastic executor and other things to flush their work after migration. + // the number was chosen arbitrary by feeling 😅 + sleep(333) shutdownProvider.shutdown(this.class) } } - protected enableShutdownAfterFinish() { + protected void enableShutdownAfterFinish() { this.shutdownAfterFinish = true; } - protected disableShutdownAfterFinish() { + protected void disableShutdownAfterFinish() { this.shutdownAfterFinish = false; } abstract void migrate() + } From 00d04bbd2d74d3ad42ee8383edde5df941e91abf Mon Sep 17 00:00:00 2001 From: Milan Mladoniczky <6153201+tuplle@users.noreply.github.com> Date: Tue, 10 Mar 2026 11:08:16 +0100 Subject: [PATCH 4/6] Update GitHub Actions dependencies and services - Upgraded `actions/checkout` from v3 to v6 across workflows for feature improvements and compatibility. - Updated `actions/setup-java` from v3 to v5 and switched JDK distribution to `temurin`, adding Maven caching where applicable. - Upgraded MongoDB image to `mongo:6` and Elasticsearch image to `elasticsearch:7.17.28` in workflows. - Removed deprecated Maven cache setup and unused steps to simplify workflows. --- .github/workflows/master-build.yml | 12 +++++------ .github/workflows/pr-build.yml | 33 +++++++++++------------------ .github/workflows/release-build.yml | 22 +++++++++---------- 3 files changed, 29 insertions(+), 38 deletions(-) diff --git a/.github/workflows/master-build.yml b/.github/workflows/master-build.yml index 79c87fd6a60..d528df3bb06 100644 --- a/.github/workflows/master-build.yml +++ b/.github/workflows/master-build.yml @@ -9,12 +9,12 @@ jobs: name: Build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: java-version: 11 distribution: 'adopt' @@ -67,12 +67,12 @@ jobs: echo $ELASTIC_SEARCH_URL curl -fsSL "$ELASTIC_SEARCH_URL/_cat/health?h=status" - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: java-version: 11 distribution: 'adopt' @@ -113,13 +113,13 @@ jobs: needs: test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: token: ${{ secrets.PUSH_DOCS }} fetch-depth: 0 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: java-version: 11 distribution: 'adopt' diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index 0005231f061..45479407461 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -8,22 +8,19 @@ jobs: name: Build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: java-version: 11 - distribution: 'adopt' + distribution: 'temurin' + cache: maven - - name: Cache Maven packages - uses: actions/cache@v3 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 + - name: Temporary remove corrupted jars + run: rm -rf ~/.m2/repository/xml-apis - name: Build run: mvn clean verify -DskipTests=true @@ -35,7 +32,7 @@ jobs: timeout-minutes: 200 services: mongo: - image: mongo:4.4 + image: mongo:6 ports: - 27017:27017 @@ -45,7 +42,7 @@ jobs: - 6379:6379 elasticsearch: - image: elasticsearch:7.17.3 + image: elasticsearch:7.17.28 ports: - 9200:9200 - 9300:9300 @@ -66,15 +63,16 @@ jobs: echo $ELASTIC_SEARCH_URL curl -fsSL "$ELASTIC_SEARCH_URL/_cat/health?h=status" - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: java-version: 11 - distribution: 'adopt' + distribution: 'temurin' + cache: maven # - name: Cache SonarCloud packages # uses: actions/cache@v3 @@ -83,13 +81,6 @@ jobs: # key: ${{ runner.os }}-sonar # restore-keys: ${{ runner.os }}-sonar - - name: Cache Maven packages - uses: actions/cache@v3 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 - - name: Generate certificates run: cd src/main/resources/certificates && openssl genrsa -out keypair.pem 4096 && openssl rsa -in keypair.pem -pubout -out public.crt && openssl pkcs8 -topk8 -inform PEM -outform DER -nocrypt -in keypair.pem -out private.der && cd ../../../.. diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index b5fdc3067e1..90d3265ebc4 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -9,12 +9,12 @@ jobs: name: Build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: java-version: 11 distribution: 'adopt' @@ -71,12 +71,12 @@ jobs: echo $ELASTIC_SEARCH_URL curl -fsSL "$ELASTIC_SEARCH_URL/_cat/health?h=status" - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: java-version: 11 distribution: 'adopt' @@ -108,7 +108,7 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - id: install-secret-key name: Install gpg secret key @@ -117,7 +117,7 @@ jobs: gpg --list-secret-keys --keyid-format LONG - name: Set up Maven Central Repository - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: java-version: 11 distribution: 'adopt' @@ -141,10 +141,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v6 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: java-version: 11 distribution: 'adopt' @@ -190,9 +190,9 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v5 with: java-version: '11' distribution: 'adopt' @@ -219,7 +219,7 @@ jobs: id-token: write security-events: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Build run: mvn clean package install -DskipTests=true From 384252c18b74cbd66e94c56f1e244e10367edf3a Mon Sep 17 00:00:00 2001 From: Milan Mladoniczky <6153201+tuplle@users.noreply.github.com> Date: Tue, 10 Mar 2026 11:45:40 +0100 Subject: [PATCH 5/6] Update Maven repositories and workflows - Added new Maven repositories for central and snapshot artifacts in `pom.xml`. - Switched JDK distribution to `temurin` and enabled Maven caching in GitHub workflows. - Upgraded MongoDB service to `mongo:6` and Elasticsearch to `elasticsearch:7.17.28`. - Updated dependencies including `xml-apis-ext` and various GitHub Actions versions for compatibility. --- .github/workflows/master-build.yml | 36 ++++++--------------- .github/workflows/pr-build.yml | 3 -- .github/workflows/release-build.yml | 49 ++++++++--------------------- pom.xml | 26 +++++++++++++++ 4 files changed, 48 insertions(+), 66 deletions(-) diff --git a/.github/workflows/master-build.yml b/.github/workflows/master-build.yml index d528df3bb06..a6400af651b 100644 --- a/.github/workflows/master-build.yml +++ b/.github/workflows/master-build.yml @@ -17,14 +17,8 @@ jobs: uses: actions/setup-java@v5 with: java-version: 11 - distribution: 'adopt' - - - name: Cache Maven packages - uses: actions/cache@v3 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 + distribution: 'temurin' + cache: maven - name: Build run: mvn clean verify -DskipTests=true @@ -36,7 +30,7 @@ jobs: timeout-minutes: 90 services: mongo: - image: mongo:4.4 + image: mongo:6 ports: - 27017:27017 @@ -46,7 +40,7 @@ jobs: - 6379:6379 elasticsearch: - image: elasticsearch:7.17.3 + image: elasticsearch:7.17.28 ports: - 9200:9200 - 9300:9300 @@ -75,7 +69,8 @@ jobs: uses: actions/setup-java@v5 with: java-version: 11 - distribution: 'adopt' + distribution: 'temurin' + cache: maven # - name: Cache SonarCloud packages # uses: actions/cache@v3 @@ -84,13 +79,6 @@ jobs: # key: ${{ runner.os }}-sonar # restore-keys: ${{ runner.os }}-sonar - - name: Cache Maven packages - uses: actions/cache@v3 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 - - name: Generate certificates run: cd src/main/resources/certificates && openssl genrsa -out keypair.pem 4096 && openssl rsa -in keypair.pem -pubout -out public.crt && openssl pkcs8 -topk8 -inform PEM -outform DER -nocrypt -in keypair.pem -out private.der && cd ../../../.. @@ -122,14 +110,8 @@ jobs: uses: actions/setup-java@v5 with: java-version: 11 - distribution: 'adopt' - - - name: Cache Maven packages - uses: actions/cache@v3 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 + distribution: 'temurin' + cache: maven - name: Build run: mvn clean package install -DskipTests=true @@ -139,7 +121,7 @@ jobs: mvn javadoc:javadoc cp -r ./target/apidocs/* ./docs/javadoc/ - - uses: EndBug/add-and-commit@v8 + - uses: EndBug/add-and-commit@v9 with: add: docs pathspec_error_handling: exitImmediately diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index 45479407461..ff3edb08caa 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -19,9 +19,6 @@ jobs: distribution: 'temurin' cache: maven - - name: Temporary remove corrupted jars - run: rm -rf ~/.m2/repository/xml-apis - - name: Build run: mvn clean verify -DskipTests=true diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 90d3265ebc4..64f8d110c21 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -17,19 +17,13 @@ jobs: uses: actions/setup-java@v5 with: java-version: 11 - distribution: 'adopt' + distribution: 'temurin' + cache: maven - name: Get Project Version from pom.xml uses: entimaniac/read-pom-version-action@1.0.0 id: getVersion - - name: Cache Maven packages - uses: actions/cache@v3 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 - - name: Build run: mvn clean verify -DskipTests=true @@ -79,14 +73,8 @@ jobs: uses: actions/setup-java@v5 with: java-version: 11 - distribution: 'adopt' - - - name: Cache Maven packages - uses: actions/cache@v3 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 + distribution: 'temurin' + cache: maven - name: Generate certificates run: cd src/main/resources/certificates && openssl genrsa -out keypair.pem 4096 && openssl rsa -in keypair.pem -pubout -out public.crt && openssl pkcs8 -topk8 -inform PEM -outform DER -nocrypt -in keypair.pem -out private.der && cd ../../../.. @@ -120,7 +108,8 @@ jobs: uses: actions/setup-java@v5 with: java-version: 11 - distribution: 'adopt' + distribution: 'temurin' + cache: maven server-id: central server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD @@ -147,20 +136,14 @@ jobs: uses: actions/setup-java@v5 with: java-version: 11 - distribution: 'adopt' - - - name: Cache Maven packages - uses: actions/cache@v3 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 + distribution: 'temurin' + cache: maven - name: Build run: mvn -P docker-build clean package install -DskipTests=true - name: Log in to Docker Hub - uses: docker/login-action@v1 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_AUTH_TOKEN }} @@ -170,14 +153,14 @@ jobs: id: getVersion - name: Push Version ${{ steps.getVersion.outputs.version }} - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v7 with: push: true tags: netgrif/application-engine:${{ steps.getVersion.outputs.version }} - name: Push Latest if: ${{ !contains(steps.getVersion.outputs.version, '-SNAPSHOT') }} - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v7 with: push: true tags: netgrif/application-engine:latest @@ -195,14 +178,8 @@ jobs: - uses: actions/setup-java@v5 with: java-version: '11' - distribution: 'adopt' - - - name: Cache Maven packages - uses: actions/cache@v3 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 + distribution: 'temurin' + cache: maven - name: Publish artifact on GitHub Packages run: mvn -B -P github-publish clean deploy -DskipTests diff --git a/pom.xml b/pom.xml index e57436ea5cc..470b48b79fe 100644 --- a/pom.xml +++ b/pom.xml @@ -91,6 +91,27 @@ + + central + https://repo.maven.apache.org/maven2 + + true + + + false + + + + Central Portal Snapshots + central-portal-snapshots + https://central.sonatype.com/repository/maven-snapshots/ + + false + + + true + + jitpack.io https://jitpack.io @@ -315,6 +336,11 @@ batik-all 1.17 + + xml-apis + xml-apis-ext + 1.3.04 + commons-io commons-io From 9f90ce5964c94854e901810e2c5cbdc94789fd5c Mon Sep 17 00:00:00 2001 From: Milan Mladoniczky <6153201+tuplle@users.noreply.github.com> Date: Tue, 10 Mar 2026 12:21:17 +0100 Subject: [PATCH 6/6] Add UserServiceTest and improve graceful shutdown handling - Introduced `UserServiceTest` to validate user existence and ID checks. - Enhanced `ApplicationShutdownProvider` to properly shut down thread pool executors for graceful application termination. --- .../ApplicationShutdownProvider.java | 8 ++++ .../engine/auth/service/UserServiceTest.java | 41 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/test/java/com/netgrif/application/engine/auth/service/UserServiceTest.java diff --git a/src/main/java/com/netgrif/application/engine/configuration/ApplicationShutdownProvider.java b/src/main/java/com/netgrif/application/engine/configuration/ApplicationShutdownProvider.java index 57c0c18a738..db24485e5d9 100644 --- a/src/main/java/com/netgrif/application/engine/configuration/ApplicationShutdownProvider.java +++ b/src/main/java/com/netgrif/application/engine/configuration/ApplicationShutdownProvider.java @@ -4,6 +4,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.context.ApplicationContext; +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; @Slf4j @@ -12,10 +14,16 @@ public class ApplicationShutdownProvider { private final ApplicationContext applicationContext; + private final TaskExecutor taskExecutor; public void shutdown(Class calledBy, int exitCode) { String className = calledBy == null ? "unknown" : calledBy.getSimpleName(); log.info("Application was signalled by {} to shutdown; exit code: {}", className, exitCode); + if (taskExecutor != null && taskExecutor instanceof ThreadPoolTaskExecutor) { + log.info("Shutting down thread pool executor"); + ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) taskExecutor; + executor.shutdown(); + } int ec = SpringApplication.exit(applicationContext, () -> exitCode); System.exit(ec); } diff --git a/src/test/java/com/netgrif/application/engine/auth/service/UserServiceTest.java b/src/test/java/com/netgrif/application/engine/auth/service/UserServiceTest.java new file mode 100644 index 00000000000..874e29fd24e --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/auth/service/UserServiceTest.java @@ -0,0 +1,41 @@ +package com.netgrif.application.engine.auth.service; + +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.auth.domain.IUser; +import com.netgrif.application.engine.auth.service.interfaces.IUserService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class UserServiceTest { + + @Autowired + private IUserService service; + + @Autowired + private TestHelper testHelper; + + @BeforeEach + void before() { + testHelper.truncateDbs(); + } + + @Test + public void shouldUserExist() { + IUser user = service.findByEmail("super@netgrif.com", true); + assertNotNull(user); + boolean userExists = service.existsById(user.getStringId()); + assertTrue(userExists); + } + +}