diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 4544626ea8..2addc10811 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -54,7 +54,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/crosschecks.yml b/.github/workflows/crosschecks.yml index 2fac41fbe8..1474e9d4ef 100644 --- a/.github/workflows/crosschecks.yml +++ b/.github/workflows/crosschecks.yml @@ -47,7 +47,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index 3c2b3a8b10..5a10de05be 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -64,7 +64,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/fit_Elasticsearch.yml b/.github/workflows/fit_Elasticsearch.yml index 1a1d4ad937..b2f24b4e4e 100644 --- a/.github/workflows/fit_Elasticsearch.yml +++ b/.github/workflows/fit_Elasticsearch.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/fit_OpenSearch.yml b/.github/workflows/fit_OpenSearch.yml index 90ed0cad66..25d5851d46 100644 --- a/.github/workflows/fit_OpenSearch.yml +++ b/.github/workflows/fit_OpenSearch.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/fit_Payara.yml b/.github/workflows/fit_Payara.yml index aaf721c5e7..7b2d697106 100644 --- a/.github/workflows/fit_Payara.yml +++ b/.github/workflows/fit_Payara.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/fit_Tomcat_PostgreSQL_JSON.yml b/.github/workflows/fit_Tomcat_PostgreSQL_JSON.yml index 4a6554b65c..2d9a5b8bcb 100644 --- a/.github/workflows/fit_Tomcat_PostgreSQL_JSON.yml +++ b/.github/workflows/fit_Tomcat_PostgreSQL_JSON.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/fit_Tomcat_PostgreSQL_XML.yml b/.github/workflows/fit_Tomcat_PostgreSQL_XML.yml index cdb2d838e9..b3153d898c 100644 --- a/.github/workflows/fit_Tomcat_PostgreSQL_XML.yml +++ b/.github/workflows/fit_Tomcat_PostgreSQL_XML.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/fit_Tomcat_PostgreSQL_YAML.yml b/.github/workflows/fit_Tomcat_PostgreSQL_YAML.yml index 2317b2db75..ba19bdd2d0 100644 --- a/.github/workflows/fit_Tomcat_PostgreSQL_YAML.yml +++ b/.github/workflows/fit_Tomcat_PostgreSQL_YAML.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/fit_WA_OIDCC4UI.yml b/.github/workflows/fit_WA_OIDCC4UI.yml index 5e891b7a95..9b079f6490 100644 --- a/.github/workflows/fit_WA_OIDCC4UI.yml +++ b/.github/workflows/fit_WA_OIDCC4UI.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/fit_WA_SAML2SP4UI.yml b/.github/workflows/fit_WA_SAML2SP4UI.yml index abba469d82..93881c49d3 100644 --- a/.github/workflows/fit_WA_SAML2SP4UI.yml +++ b/.github/workflows/fit_WA_SAML2SP4UI.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/fit_WA_SRA_CASClient.yml b/.github/workflows/fit_WA_SRA_CASClient.yml index 9090b001c7..f32f124ff1 100644 --- a/.github/workflows/fit_WA_SRA_CASClient.yml +++ b/.github/workflows/fit_WA_SRA_CASClient.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/fit_WA_SRA_OAuth2.yml b/.github/workflows/fit_WA_SRA_OAuth2.yml index 1a892f5ef9..e016731bf4 100644 --- a/.github/workflows/fit_WA_SRA_OAuth2.yml +++ b/.github/workflows/fit_WA_SRA_OAuth2.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/fit_WA_SRA_OIDC.yml b/.github/workflows/fit_WA_SRA_OIDC.yml index 52d04df2b0..0360b68d72 100644 --- a/.github/workflows/fit_WA_SRA_OIDC.yml +++ b/.github/workflows/fit_WA_SRA_OIDC.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/fit_WA_SRA_SAML2.yml b/.github/workflows/fit_WA_SRA_SAML2.yml index 2d6fe0030c..b60f464740 100644 --- a/.github/workflows/fit_WA_SRA_SAML2.yml +++ b/.github/workflows/fit_WA_SRA_SAML2.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/fit_Wildfly.yml b/.github/workflows/fit_Wildfly.yml index 41bd06bb28..5e16517473 100644 --- a/.github/workflows/fit_Wildfly.yml +++ b/.github/workflows/fit_Wildfly.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/fit_Zookeeper.yml b/.github/workflows/fit_Zookeeper.yml index 5afa0226e5..c2ff40d05e 100644 --- a/.github/workflows/fit_Zookeeper.yml +++ b/.github/workflows/fit_Zookeeper.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/mariadb.yml b/.github/workflows/mariadb.yml index 482bca4558..8c2e2b7b1e 100644 --- a/.github/workflows/mariadb.yml +++ b/.github/workflows/mariadb.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/mysql.yml b/.github/workflows/mysql.yml index 4897ea71e4..30997151f9 100644 --- a/.github/workflows/mysql.yml +++ b/.github/workflows/mysql.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/neo4j.yml b/.github/workflows/neo4j.yml index 06af0eb436..d698236c3f 100644 --- a/.github/workflows/neo4j.yml +++ b/.github/workflows/neo4j.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/.github/workflows/oracle.yml b/.github/workflows/oracle.yml index e4958d6643..128a47b4d5 100644 --- a/.github/workflows/oracle.yml +++ b/.github/workflows/oracle.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Maven uses: stCarolas/setup-maven@v5 with: - maven-version: 3.9.9 + maven-version: 3.9.6 - uses: actions/cache@v4 with: path: ~/.m2/repository diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/DefaultJobManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/DefaultJobManager.java index cb0064cf32..1b11094182 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/DefaultJobManager.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/DefaultJobManager.java @@ -103,11 +103,13 @@ public DefaultJobManager( @Override public boolean isRunning(final String jobName) { - boolean locked = jobStatusDAO.lock(jobName); - if (locked) { - jobStatusDAO.unlock(jobName); + synchronized (jobName) { + boolean locked = jobStatusDAO.lock(jobName); + if (locked) { + jobStatusDAO.unlock(jobName); + } + return !locked; } - return !locked; } protected void registerJob( @@ -247,28 +249,21 @@ public int getOrder() { @Transactional @Override public void load(final String domain) { - String notificationJobCronExp = AuthContextUtils.callAsAdmin(SyncopeConstants.MASTER_DOMAIN, () -> { - String result = StringUtils.EMPTY; - - String conf = confParamOps.get( - SyncopeConstants.MASTER_DOMAIN, "notificationjob.cronExpression", null, String.class); - if (conf == null) { - result = NotificationJob.DEFAULT_CRON_EXP; - } else if (!StringUtils.EMPTY.equals(conf)) { - result = conf; - } - return result; - }); - AuthContextUtils.runAsAdmin(domain, () -> { // 1. jobs for SchedTasks Set tasks = new HashSet<>(taskDAO.findAll(TaskType.SCHEDULED)); tasks.addAll(taskDAO.findAll(TaskType.PULL)); tasks.addAll(taskDAO.findAll(TaskType.PUSH)); + tasks.addAll(taskDAO.findAll(TaskType.MACRO)); + tasks.addAll(taskDAO.findAll(TaskType.LIVE_SYNC)); boolean loadException = false; for (Iterator it = tasks.iterator(); it.hasNext() && !loadException;) { SchedTask task = it.next(); + + LOG.debug("Loading job for {} Task {} {}", + taskUtilsFactory.getInstance(task).getType(), task.getKey(), task.getName()); + try { register(domain, task, task.getStartAt(), securityProperties.getAdminUser(), false, Map.of()); } catch (Exception e) { @@ -278,11 +273,14 @@ public void load(final String domain) { } if (loadException) { - LOG.debug("Errors while loading job instances for tasks, aborting"); + LOG.error("Errors while loading job for tasks, aborting"); } else { // 2. jobs for Reports for (Iterator it = reportDAO.findAll().iterator(); it.hasNext() && !loadException;) { Report report = it.next(); + + LOG.debug("Loading job for Report {} {}", report.getKey(), report.getName()); + try { register(domain, report, null, securityProperties.getAdminUser(), false); } catch (Exception e) { @@ -292,12 +290,25 @@ public void load(final String domain) { } if (loadException) { - LOG.debug("Errors while loading job instances for reports, aborting"); + LOG.error("Errors while loading job for reports, aborting"); } } }); if (SyncopeConstants.MASTER_DOMAIN.equals(domain)) { + String notificationJobCronExp = AuthContextUtils.callAsAdmin(SyncopeConstants.MASTER_DOMAIN, () -> { + String result = StringUtils.EMPTY; + + String conf = confParamOps.get( + SyncopeConstants.MASTER_DOMAIN, "notificationjob.cronExpression", null, String.class); + if (conf == null) { + result = NotificationJob.DEFAULT_CRON_EXP; + } else if (!StringUtils.EMPTY.equals(conf)) { + result = conf; + } + return result; + }); + // 3. NotificationJob if (StringUtils.isBlank(notificationJobCronExp)) { LOG.debug("Empty value provided for {}'s cron, not scheduling", NotificationJob.class.getSimpleName()); @@ -348,21 +359,28 @@ public void unload(final String domain) { Set tasks = new HashSet<>(taskDAO.findAll(TaskType.SCHEDULED)); tasks.addAll(taskDAO.findAll(TaskType.PULL)); tasks.addAll(taskDAO.findAll(TaskType.PUSH)); + tasks.addAll(taskDAO.findAll(TaskType.MACRO)); + tasks.addAll(taskDAO.findAll(TaskType.LIVE_SYNC)); tasks.forEach(task -> { + LOG.debug("Unloading job for {} Task {} {}", + taskUtilsFactory.getInstance(task).getType(), task.getKey(), task.getName()); + try { unregister(task); } catch (Exception e) { - LOG.error("While unloading job instance for task {}", task.getKey(), e); + LOG.error("While unloading job for task {}", task.getKey(), e); } }); // 2. jobs for Reports reportDAO.findAll().forEach(report -> { + LOG.debug("Unloading job for Report {} {}", report.getKey(), report.getName()); + try { unregister(report); } catch (Exception e) { - LOG.error("While unloading job instance for report {}", report.getName(), e); + LOG.error("While unloading job for report {}", report.getName(), e); } }); }); diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/Job.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/Job.java index 054530f619..a30be09722 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/Job.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/Job.java @@ -57,18 +57,27 @@ public void run() { AuthContextUtils.getWho(), false))); - if (AuthContextUtils.callAsAdmin(context.getDomain(), () -> jobStatusDAO.lock(context.getJobName()))) { - LOG.debug("Job {} locked, starting execution", context.getJobName()); + boolean locked = false; + try { + locked = AuthContextUtils.callAsAdmin(context.getDomain(), () -> jobStatusDAO.lock(context.getJobName())); + } catch (Exception e) { + LOG.debug("While attempting to lock job {}", context.getJobName(), e); + } + if (!locked) { + LOG.debug("Could not lock job {}, skipping execution", context.getJobName()); + return; + } + + LOG.debug("Job {} locked, starting execution", context.getJobName()); + + try { + execute(context); + } catch (JobExecutionException e) { + LOG.error("While executing job {}", context.getJobName(), e); + } finally { + LOG.debug("Job {} execution completed", context.getJobName()); - try { - execute(context); - } catch (JobExecutionException e) { - LOG.error("While executing job {}", context.getJobName(), e); - } finally { - AuthContextUtils.runAsAdmin(context.getDomain(), () -> jobStatusDAO.unlock(context.getJobName())); - } - } else { - LOG.info("Could not lock job {}, skipping execution", context.getJobName()); + AuthContextUtils.runAsAdmin(context.getDomain(), () -> jobStatusDAO.unlock(context.getJobName())); } } } diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SyncopeTaskScheduler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SyncopeTaskScheduler.java index c659c80c70..17a35ce8e0 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SyncopeTaskScheduler.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SyncopeTaskScheduler.java @@ -24,8 +24,10 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.apache.commons.lang3.tuple.MutablePair; import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.core.persistence.api.dao.JobStatusDAO; import org.apache.syncope.core.provisioning.api.job.StoppableSchedTaskJobDelegate; @@ -38,78 +40,63 @@ public class SyncopeTaskScheduler { + public static final String CACHE = "jobCache"; + protected static final Logger LOG = LoggerFactory.getLogger(SyncopeTaskScheduler.class); protected final TaskScheduler scheduler; protected final JobStatusDAO jobStatusDAO; - protected final Map, Pair>> jobs = new ConcurrentHashMap<>(); + protected final Map, Pair>>> jobs = new ConcurrentHashMap<>(); public SyncopeTaskScheduler(final TaskScheduler scheduler, final JobStatusDAO jobStatusDAO) { this.scheduler = scheduler; this.jobStatusDAO = jobStatusDAO; } - public void register(final Job job) { - jobs.put( + protected void register(final Job job, final Optional> future) { + jobs.putIfAbsent( Pair.of(job.getContext().getDomain(), job.getContext().getJobName()), - Pair.of(job, null)); - } - - public void start(final String domain, final String jobName) { - Optional.ofNullable(jobs.get(Pair.of(domain, jobName))). - ifPresent(pair -> schedule(pair.getLeft(), Instant.now())); + MutablePair.of(job, future)); } - public void start(final Job job) { - schedule(job, Instant.now()); + public void register(final Job job) { + register(job, Optional.empty()); } public void schedule(final Job job, final CronTrigger trigger) { - ScheduledFuture future = scheduler.schedule(job, trigger); - jobs.put( - Pair.of(job.getContext().getDomain(), job.getContext().getJobName()), - Pair.of(job, future)); + register(job, Optional.of(scheduler.schedule(job, trigger))); } public void schedule(final Job job, final Instant startTime) { - ScheduledFuture future = scheduler.schedule(job, startTime); - jobs.put( - Pair.of(job.getContext().getDomain(), job.getContext().getJobName()), - Pair.of(job, future)); + register(job, Optional.of(scheduler.schedule(job, startTime))); } public boolean contains(final String domain, final String jobName) { return jobs.containsKey(Pair.of(domain, jobName)); } - public Optional> getJobClass(final String domain, final String jobName) { - return Optional.ofNullable(jobs.get(Pair.of(domain, jobName))). - map(pair -> AopUtils.getTargetClass(pair.getLeft())); - } - - public Optional getNextTrigger(final String domain, final String jobName) { - return Optional.ofNullable(jobs.get(Pair.of(domain, jobName))). - filter(pair -> pair.getRight() != null). - map(pair -> pair.getRight().getDelay(TimeUnit.SECONDS)). - filter(delay -> delay > 0). - map(delay -> OffsetDateTime.now().plusSeconds(delay)); + public void start(final String domain, final String jobName) { + Optional.ofNullable(jobs.get(Pair.of(domain, jobName))). + filter(pair -> pair.getRight().map(Future::isDone).orElse(true)). + ifPresent(pair -> pair.setValue(Optional.of(scheduler.schedule(pair.getLeft(), Instant.now())))); } public void cancel(final String domain, final String jobName) { Optional.ofNullable(jobs.get(Pair.of(domain, jobName))).ifPresent(pair -> { - boolean mayInterruptIfRunning = true; + boolean mayInterruptIfRunning; if (pair.getLeft() instanceof TaskJob taskJob && taskJob.getDelegate() instanceof StoppableSchedTaskJobDelegate stoppable) { stoppable.stop(); mayInterruptIfRunning = false; + } else { + mayInterruptIfRunning = true; } - if (pair.getRight() != null) { - pair.getRight().cancel(mayInterruptIfRunning); - } + pair.getRight().ifPresent(f -> f.cancel(mayInterruptIfRunning)); + pair.setValue(Optional.empty()); }); } @@ -118,6 +105,20 @@ public void delete(final String domain, final String jobName) { AuthContextUtils.runAsAdmin(domain, () -> jobStatusDAO.unlock(jobName)); } + public Optional> getJobClass(final String domain, final String jobName) { + return Optional.ofNullable(jobs.get(Pair.of(domain, jobName))). + map(pair -> AopUtils.getTargetClass(pair.getLeft())); + } + + public Optional getNextTrigger(final String domain, final String jobName) { + return Optional.ofNullable(jobs.get(Pair.of(domain, jobName))). + filter(pair -> pair.getRight().map(f -> !f.isDone()).orElse(false)). + flatMap(Pair::getRight). + map(f -> f.getDelay(TimeUnit.SECONDS)). + filter(delay -> delay > 0). + map(delay -> OffsetDateTime.now().plusSeconds(delay)); + } + public List getJobNames(final String domain) { return jobs.keySet().stream().filter(pair -> domain.equals(pair.getLeft())).map(Pair::getRight).toList(); } diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SchedTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SchedTaskITCase.java index b850f47cce..7a8eb99d63 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SchedTaskITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SchedTaskITCase.java @@ -28,11 +28,9 @@ import jakarta.ws.rs.core.Response; import java.time.OffsetDateTime; -import java.util.Comparator; import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; import org.apache.syncope.common.lib.to.ExecTO; import org.apache.syncope.common.lib.to.ImplementationTO; @@ -41,10 +39,10 @@ import org.apache.syncope.common.lib.to.PullTaskTO; import org.apache.syncope.common.lib.to.PushTaskTO; import org.apache.syncope.common.lib.to.SchedTaskTO; -import org.apache.syncope.common.lib.to.TaskTO; import org.apache.syncope.common.lib.types.IdRepoImplementationType; import org.apache.syncope.common.lib.types.JobAction; import org.apache.syncope.common.lib.types.TaskType; +import org.apache.syncope.common.rest.api.RESTHeaders; import org.apache.syncope.common.rest.api.beans.ExecQuery; import org.apache.syncope.common.rest.api.beans.ExecSpecs; import org.apache.syncope.common.rest.api.beans.TaskQuery; @@ -97,40 +95,74 @@ public void deferred() { SchedTaskTO task = new SchedTaskTO(); task.setActive(true); - task.setName("deferred"); + task.setName("deferred" + getUUIDString()); task.setJobDelegate(taskJobDelegate.getKey()); Response response = TASK_SERVICE.create(TaskType.SCHEDULED, task); - task = getObject(response.getLocation(), TaskService.class, SchedTaskTO.class); - assertNotNull(task); - String taskKey = task.getKey(); - assertNotNull(task); + String taskKey = response.getHeaderString(RESTHeaders.RESOURCE_KEY); + assertNotNull(taskKey); OffsetDateTime initial = OffsetDateTime.now(); OffsetDateTime later = initial.plusSeconds(2); - AtomicReference taskTO = new AtomicReference<>(task); - int preSyncSize = taskTO.get().getExecutions().size(); - ExecTO execution = TASK_SERVICE.execute(new ExecSpecs.Builder().key(task.getKey()).startAt(later).build()); - assertNotNull(execution.getExecutor()); + TASK_SERVICE.execute(new ExecSpecs.Builder().key(taskKey).startAt(later).build()); await().atMost(MAX_WAIT_SECONDS, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).until(() -> { try { - taskTO.set(TASK_SERVICE.read(TaskType.SCHEDULED, taskKey, true)); - return preSyncSize < taskTO.get().getExecutions().size(); + return !TASK_SERVICE.read(TaskType.SCHEDULED, taskKey, true).getExecutions().isEmpty(); } catch (Exception e) { return false; } }); - PagedResult execs = TASK_SERVICE.listExecutions(new ExecQuery.Builder().key(task.getKey()).build()); - assertEquals(preSyncSize + 1, execs.getTotalCount()); + PagedResult execs = TASK_SERVICE.listExecutions(new ExecQuery.Builder().key(taskKey).build()); + assertEquals(1, execs.getTotalCount()); - ExecTO exec = execs.getResult().stream(). - sorted(Comparator.comparing(ExecTO::getStart).reversed()).findFirst().orElseThrow(); - assertTrue(exec.getStart().isAfter(initial)); + assertTrue(execs.getResult().get(0).getStart().isAfter(initial)); // round 1 sec for safety - assertTrue(exec.getStart().plusSeconds(1).isAfter(later)); + assertTrue(execs.getResult().get(0).getStart().plusSeconds(1).isAfter(later)); + } + + @Test + public void multistart() { + ImplementationTO taskJobDelegate = IMPLEMENTATION_SERVICE.read( + IdRepoImplementationType.TASKJOB_DELEGATE, TestSampleJobDelegate.class.getSimpleName()); + assertNotNull(taskJobDelegate); + + SchedTaskTO task = new SchedTaskTO(); + task.setActive(true); + task.setName("multistart" + getUUIDString()); + task.setJobDelegate(taskJobDelegate.getKey()); + + Response response = TASK_SERVICE.create(TaskType.SCHEDULED, task); + String taskKey = response.getHeaderString(RESTHeaders.RESOURCE_KEY); + assertNotNull(taskKey); + + TASK_SERVICE.actionJob(taskKey, JobAction.START); + + await().atMost(MAX_WAIT_SECONDS, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).until(() -> { + try { + return !TASK_SERVICE.read(TaskType.SCHEDULED, taskKey, true).getExecutions().isEmpty(); + } catch (Exception e) { + return false; + } + }); + + PagedResult execs = TASK_SERVICE.listExecutions(new ExecQuery.Builder().key(taskKey).build()); + assertEquals(1, execs.getTotalCount()); + + TASK_SERVICE.actionJob(taskKey, JobAction.START); + + await().atMost(MAX_WAIT_SECONDS, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).until(() -> { + try { + return TASK_SERVICE.read(TaskType.SCHEDULED, taskKey, true).getExecutions().size() >= 2; + } catch (Exception e) { + return false; + } + }); + + execs = TASK_SERVICE.listExecutions(new ExecQuery.Builder().key(taskKey).build()); + assertEquals(2, execs.getTotalCount()); } @Test diff --git a/pom.xml b/pom.xml index 22fe9069ce..0b42cde948 100644 --- a/pom.xml +++ b/pom.xml @@ -412,7 +412,7 @@ under the License. 4.1.0 1.79 - 9.48 + 10.0 3.3.7 4.1.6 @@ -1741,7 +1741,7 @@ under the License. ${targetJdk} - 3.9.9 + 3.9.5 @@ -1816,7 +1816,7 @@ under the License. org.codehaus.gmavenplus gmavenplus-plugin - 4.1.0 + 4.1.1 org.apache.groovy @@ -2332,7 +2332,7 @@ under the License. - 5.14.2 + 5.15.2 1.15.11