This is an automated email from the ASF dual-hosted git repository.
ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/master by this push:
new 7cbb7e21af Improving Notification job management
7cbb7e21af is described below
commit 7cbb7e21af76cacc4d6de6a820de5f7944c8ec83
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Mon May 25 15:30:49 2026 +0200
Improving Notification job management
---
.../syncope/core/persistence/api/dao/TaskDAO.java | 4 +-
.../core/persistence/jpa/dao/JPATaskDAO.java | 126 ++++++++++++---------
.../core/persistence/jpa/inner/TaskTest.java | 4 +-
.../core/persistence/neo4j/dao/Neo4jTaskDAO.java | 104 ++++++++++-------
.../core/persistence/neo4j/inner/TaskTest.java | 4 +-
.../api/notification/NotificationManager.java | 3 +-
.../AbstractNotificationJobDelegate.java | 78 ++++++++-----
.../notification/DefaultNotificationManager.java | 10 +-
pom.xml | 2 +-
src/site/xdoc/security.xml | 84 ++++++++++++++
10 files changed, 291 insertions(+), 128 deletions(-)
diff --git
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/TaskDAO.java
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/TaskDAO.java
index ce496456f4..f0d3c466d5 100644
---
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/TaskDAO.java
+++
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/TaskDAO.java
@@ -54,7 +54,9 @@ public interface TaskDAO extends DAO<Task<?>> {
List<MacroTask> findByRealm(Realm realm);
- <T extends Task<T>> List<T> findToExec(TaskType type);
+ long countToExec(TaskType type);
+
+ <T extends Task<T>> List<T> findToExec(TaskType type, Pageable pageable);
<T extends Task<T>> List<T> findAll(TaskType type);
diff --git
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
index c94ed7853a..17bd64981c 100644
---
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
+++
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
@@ -231,8 +231,72 @@ public class JPATaskDAO implements TaskDAO {
}
@Override
+ public long countToExec(final TaskType type) {
+ StringBuilder queryString = buildFindAllQuery(type).append("AND ");
+
+ if (type == TaskType.NOTIFICATION) {
+ queryString.append("t.executed = false ");
+ } else {
+ queryString.append("t.executions IS EMPTY ");
+ }
+
+ Query query = entityManager.createQuery(Strings.CS.replaceOnce(
+ queryString.toString(),
+ "SELECT t FROM ",
+ "SELECT COUNT(t) FROM "));
+
+ return ((Number) query.getSingleResult()).longValue();
+ }
+
+ protected String toOrderByStatement(
+ final Class<? extends Task<?>> beanClass,
+ final Stream<Sort.Order> orderByClauses,
+ final String prefix) {
+
+ StringBuilder subStatement = new StringBuilder();
+ orderByClauses.forEach(clause -> {
+ String field = clause.getProperty().trim();
+ switch (field) {
+ case "latestExecStatus":
+ field = "status";
+ break;
+
+ case "start":
+ field = "startDate";
+ break;
+
+ case "end":
+ field = "endDate";
+ break;
+
+ default:
+ Field beanField = ReflectionUtils.findField(beanClass,
field);
+ if (beanField != null
+ && (beanField.getAnnotation(ManyToOne.class) !=
null
+ || beanField.getAnnotation(OneToMany.class) != null
+ || beanField.getAnnotation(OneToOne.class) !=
null)) {
+
+ field += "_id";
+ }
+ }
+
+ subStatement.append(prefix).append(field).append('
').append(clause.getDirection().name()).append(',');
+ });
+
+ StringBuilder statement = new StringBuilder(" ORDER BY ");
+ if (subStatement.length() == 0) {
+ statement.append(prefix).append("id DESC");
+ } else {
+ subStatement.deleteCharAt(subStatement.length() - 1);
+ statement.append(subStatement);
+ }
+
+ return statement.toString();
+ }
+
@SuppressWarnings("unchecked")
- public <T extends Task<T>> List<T> findToExec(final TaskType type) {
+ @Override
+ public <T extends Task<T>> List<T> findToExec(final TaskType type, final
Pageable pageable) {
StringBuilder queryString = buildFindAllQuery(type).append("AND ");
if (type == TaskType.NOTIFICATION) {
@@ -240,9 +304,17 @@ public class JPATaskDAO implements TaskDAO {
} else {
queryString.append("t.executions IS EMPTY ");
}
- queryString.append("ORDER BY t.id DESC");
+
+ queryString.append(toOrderByStatement(
+ taskUtilsFactory.getInstance(type).getTaskEntity(),
pageable.getSort().stream(), "t."));
Query query = entityManager.createQuery(queryString.toString());
+
+ if (pageable.isPaged()) {
+ query.setFirstResult(pageable.getPageSize() *
pageable.getPageNumber());
+ query.setMaxResults(pageable.getPageSize());
+ }
+
return query.getResultList();
}
@@ -352,54 +424,6 @@ public class JPATaskDAO implements TaskDAO {
return queryString;
}
- protected String toOrderByStatement(
- final Class<? extends Task<?>> beanClass,
- final Stream<Sort.Order> orderByClauses) {
-
- StringBuilder statement = new StringBuilder();
-
- statement.append(" ORDER BY ");
-
- StringBuilder subStatement = new StringBuilder();
- orderByClauses.forEach(clause -> {
- String field = clause.getProperty().trim();
- switch (field) {
- case "latestExecStatus":
- field = "status";
- break;
-
- case "start":
- field = "startDate";
- break;
-
- case "end":
- field = "endDate";
- break;
-
- default:
- Field beanField = ReflectionUtils.findField(beanClass,
field);
- if (beanField != null
- && (beanField.getAnnotation(ManyToOne.class) !=
null
- || beanField.getAnnotation(OneToMany.class) != null
- || beanField.getAnnotation(OneToOne.class) !=
null)) {
-
- field += "_id";
- }
- }
-
- subStatement.append(field).append('
').append(clause.getDirection().name()).append(',');
- });
-
- if (subStatement.length() == 0) {
- statement.append("id DESC");
- } else {
- subStatement.deleteCharAt(subStatement.length() - 1);
- statement.append(subStatement);
- }
-
- return statement.toString();
- }
-
@SuppressWarnings("unchecked")
@Override
public <T extends Task<T>> List<T> findAll(
@@ -448,7 +472,7 @@ public class JPATaskDAO implements TaskDAO {
}
queryString.append(toOrderByStatement(
- taskUtilsFactory.getInstance(type).getTaskEntity(),
pageable.getSort().stream()));
+ taskUtilsFactory.getInstance(type).getTaskEntity(),
pageable.getSort().stream(), ""));
Query query = entityManager.createNativeQuery(queryString.toString());
diff --git
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java
index 2da9bbbf96..37b83ca351 100644
---
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java
+++
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskTest.java
@@ -90,7 +90,9 @@ public class TaskTest extends AbstractTest {
@Test
public void findWithoutExecs() {
- List<PropagationTask> tasks = taskDAO.findToExec(TaskType.PROPAGATION);
+ assertEquals(3, taskDAO.countToExec(TaskType.PROPAGATION));
+
+ List<PropagationTask> tasks = taskDAO.findToExec(TaskType.PROPAGATION,
Pageable.unpaged());
assertNotNull(tasks);
assertEquals(3, tasks.size());
}
diff --git
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jTaskDAO.java
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jTaskDAO.java
index 0bbea61c08..97d6195d11 100644
---
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jTaskDAO.java
+++
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jTaskDAO.java
@@ -230,8 +230,61 @@ public class Neo4jTaskDAO extends AbstractDAO implements
TaskDAO {
}
@Override
+ public long countToExec(final TaskType type) {
+ TaskUtils taskUtils = taskUtilsFactory.getInstance(type);
+ StringBuilder query = new StringBuilder("MATCH (n:" +
taskUtils.getTaskStorage() + ") WHERE ");
+
+ if (type == TaskType.NOTIFICATION) {
+ query.append("n.executed = false ");
+ } else {
+ query.append("(n)-[:").append(execRelationship(type)).append("]-()
");
+ }
+
+ query.append(" RETURN COUNT(DISTINCT n)");
+
+ return neo4jTemplate.count(query.toString());
+ }
+
+ protected String toOrderByStatement(
+ final Class<? extends Task<?>> beanClass,
+ final Stream<Sort.Order> orderByClauses) {
+
+ StringBuilder subStatement = new StringBuilder();
+ orderByClauses.forEach(clause -> {
+ String field = clause.getProperty().trim();
+ switch (field) {
+ case "latestExecStatus":
+ field = "status";
+ break;
+
+ case "start":
+ field = "startDate";
+ break;
+
+ case "end":
+ field = "endDate";
+ break;
+
+ default:
+ }
+
+ subStatement.append("p.").append(field).append('
').append(clause.getDirection().name()).append(',');
+ });
+
+ StringBuilder statement = new StringBuilder(" ORDER BY ");
+ if (subStatement.length() == 0) {
+ statement.append("n.id DESC");
+ } else {
+ subStatement.deleteCharAt(subStatement.length() - 1);
+ statement.append(subStatement);
+ }
+
+ return statement.toString();
+ }
+
@SuppressWarnings("unchecked")
- public <T extends Task<T>> List<T> findToExec(final TaskType type) {
+ @Override
+ public <T extends Task<T>> List<T> findToExec(final TaskType type, final
Pageable pageable) {
TaskUtils taskUtils = taskUtilsFactory.getInstance(type);
StringBuilder query = new StringBuilder("MATCH (n:" +
taskUtils.getTaskStorage() + ") WHERE ");
@@ -240,7 +293,14 @@ public class Neo4jTaskDAO extends AbstractDAO implements
TaskDAO {
} else {
query.append("(n)-[:").append(execRelationship(type)).append("]-()
");
}
- query.append("RETURN n.id ORDER BY n.id DESC");
+
+ query.append("RETURN n.id ").
+ append(toOrderByStatement(taskUtils.getTaskEntity(),
pageable.getSort().get()));
+
+ if (pageable.isPaged()) {
+ query.append(" SKIP ").append(pageable.getPageSize() *
pageable.getPageNumber()).
+ append(" LIMIT ").append(pageable.getPageSize());
+ }
return toList(neo4jClient.query(query.toString()).fetch().all(),
"n.id",
@@ -382,46 +442,6 @@ public class Neo4jTaskDAO extends AbstractDAO implements
TaskDAO {
return neo4jTemplate.count(query.toString(), parameters);
}
- protected String toOrderByStatement(
- final Class<? extends Task<?>> beanClass,
- final Stream<Sort.Order> orderByClauses) {
-
- StringBuilder statement = new StringBuilder();
-
- statement.append("ORDER BY ");
-
- StringBuilder subStatement = new StringBuilder();
- orderByClauses.forEach(clause -> {
- String field = clause.getProperty().trim();
- switch (field) {
- case "latestExecStatus":
- field = "status";
- break;
-
- case "start":
- field = "startDate";
- break;
-
- case "end":
- field = "endDate";
- break;
-
- default:
- }
-
- subStatement.append("p.").append(field).append('
').append(clause.getDirection().name()).append(',');
- });
-
- if (subStatement.length() == 0) {
- statement.append("n.id DESC");
- } else {
- subStatement.deleteCharAt(subStatement.length() - 1);
- statement.append(subStatement);
- }
-
- return statement.toString();
- }
-
@SuppressWarnings("unchecked")
@Override
public <T extends Task<T>> List<T> findAll(
diff --git
a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/TaskTest.java
b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/TaskTest.java
index fb1d5bb677..8659f57cc2 100644
---
a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/TaskTest.java
+++
b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/TaskTest.java
@@ -90,7 +90,9 @@ public class TaskTest extends AbstractTest {
@Test
public void findWithoutExecs() {
- List<PropagationTask> tasks = taskDAO.findToExec(TaskType.PROPAGATION);
+ assertEquals(3, taskDAO.countToExec(TaskType.PROPAGATION));
+
+ List<PropagationTask> tasks = taskDAO.findToExec(TaskType.PROPAGATION,
Pageable.unpaged());
assertNotNull(tasks);
assertEquals(3, tasks.size());
}
diff --git
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/notification/NotificationManager.java
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/notification/NotificationManager.java
index 045e8d915f..586692adfb 100644
---
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/notification/NotificationManager.java
+++
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/notification/NotificationManager.java
@@ -101,8 +101,9 @@ public interface NotificationManager {
/**
* Store execution of a NotificationTask.
*
+ * @param taskKey task to be updated
* @param execution task execution.
* @return merged task execution.
*/
- TaskExec<NotificationTask> storeExec(TaskExec<NotificationTask> execution);
+ TaskExec<NotificationTask> storeExec(String taskKey,
TaskExec<NotificationTask> execution);
}
diff --git
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/AbstractNotificationJobDelegate.java
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/AbstractNotificationJobDelegate.java
index 6d5a38e2d8..f82d0429e9 100644
---
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/AbstractNotificationJobDelegate.java
+++
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/notification/AbstractNotificationJobDelegate.java
@@ -26,6 +26,7 @@ import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.types.OpEvent;
import org.apache.syncope.common.lib.types.TaskType;
import org.apache.syncope.common.lib.types.TraceLevel;
+import org.apache.syncope.core.persistence.api.dao.AnyDAO;
import org.apache.syncope.core.persistence.api.dao.TaskDAO;
import org.apache.syncope.core.persistence.api.entity.task.NotificationTask;
import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
@@ -34,15 +35,18 @@ import
org.apache.syncope.core.persistence.api.utils.ExceptionUtils2;
import org.apache.syncope.core.provisioning.api.AuditManager;
import org.apache.syncope.core.provisioning.api.event.JobStatusEvent;
import org.apache.syncope.core.provisioning.api.job.JobManager;
+import org.apache.syncope.core.provisioning.api.job.StoppableJobDelegate;
import
org.apache.syncope.core.provisioning.api.notification.NotificationJobDelegate;
import
org.apache.syncope.core.provisioning.api.notification.NotificationManager;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
import org.springframework.transaction.annotation.Transactional;
-public abstract class AbstractNotificationJobDelegate implements
NotificationJobDelegate {
+public abstract class AbstractNotificationJobDelegate implements
NotificationJobDelegate, StoppableJobDelegate {
protected static final Logger LOG =
LoggerFactory.getLogger(NotificationJobDelegate.class);
@@ -58,6 +62,8 @@ public abstract class AbstractNotificationJobDelegate
implements NotificationJob
protected final ApplicationEventPublisher publisher;
+ protected boolean stopRequested = false;
+
protected AbstractNotificationJobDelegate(
final ConfParamOps confParamOps,
final TaskDAO taskDAO,
@@ -74,6 +80,11 @@ public abstract class AbstractNotificationJobDelegate
implements NotificationJob
this.publisher = publisher;
}
+ @Override
+ public void stop() {
+ stopRequested = true;
+ }
+
protected void setStatus(final String status) {
publisher.publishEvent(new JobStatusEvent(
this, AuthContextUtils.getDomain(),
JobManager.NOTIFICATION_JOB, status));
@@ -86,7 +97,6 @@ public abstract class AbstractNotificationJobDelegate
implements NotificationJob
@Override
public TaskExec<NotificationTask> executeSingle(final NotificationTask
task, final String executor) {
TaskExec<NotificationTask> execution =
taskUtilsFactory.getInstance(TaskType.NOTIFICATION).newTaskExec();
- execution.setTask(task);
execution.setStart(OffsetDateTime.now());
execution.setExecutor(executor);
boolean retryPossible = true;
@@ -115,9 +125,9 @@ public abstract class AbstractNotificationJobDelegate
implements NotificationJob
setStatus("Sending notifications to " + task.getRecipients());
- for (String to : task.getRecipients()) {
+ for (String recipient : task.getRecipients()) {
try {
- notify(to, task, execution);
+ notify(recipient, task, execution);
notificationManager.createTasks(
AuthContextUtils.getWho(),
@@ -129,9 +139,9 @@ public abstract class AbstractNotificationJobDelegate
implements NotificationJob
null,
null,
task,
- "Successfully sent notification to " + to);
+ "Successfully sent notification to " + recipient);
} catch (Exception e) {
- LOG.error("Could not send out notification", e);
+ LOG.error("Could not send out notification to {}",
recipient, e);
execution.setStatus(NotificationJob.Status.NOT_SENT.name());
if (task.getTraceLevel().ordinal() >=
TraceLevel.FAILURES.ordinal()) {
@@ -148,22 +158,22 @@ public abstract class AbstractNotificationJobDelegate
implements NotificationJob
null,
null,
task,
- "Could not send notification to " + to, e);
+ "Could not send notification to " + recipient, e);
}
execution.setEnd(OffsetDateTime.now());
}
}
- if (hasToBeRegistered(execution)) {
- execution = notificationManager.storeExec(execution);
+ if (hasToBeRegistered(task, execution)) {
+ execution = notificationManager.storeExec(task.getKey(),
execution);
if (retryPossible
- && (NotificationJob.Status.valueOf(execution.getStatus())
== NotificationJob.Status.NOT_SENT)) {
+ && NotificationJob.Status.valueOf(execution.getStatus())
== NotificationJob.Status.NOT_SENT) {
- handleRetries(execution);
+ handleRetries(task, execution);
}
} else {
- notificationManager.setTaskExecuted(execution.getTask().getKey(),
true);
+ notificationManager.setTaskExecuted(task.getKey(), true);
}
return execution;
@@ -172,20 +182,32 @@ public abstract class AbstractNotificationJobDelegate
implements NotificationJob
@Transactional
@Override
public void execute(final String executor) {
- List<NotificationTask> tasks =
taskDAO.findToExec(TaskType.NOTIFICATION);
+ stopRequested = false;
+
+ long count = taskDAO.countToExec(TaskType.NOTIFICATION);
+ long pages = (count / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
+ for (int page = 0; page < pages && !stopRequested; page++) {
+ List<NotificationTask> tasks = taskDAO.findToExec(
+ TaskType.NOTIFICATION,
+ PageRequest.of(page, AnyDAO.DEFAULT_PAGE_SIZE,
Sort.Direction.ASC, "id"));
- setStatus("Sending out " + tasks.size() + " notifications");
+ setStatus("Sending out " + tasks.size() + " notifications");
- for (int i = 0; i < tasks.size(); i++) {
- LOG.debug("Found notification task {} to be executed:
starting...", tasks.get(i));
- executeSingle(tasks.get(i), executor);
- LOG.debug("Notification task {} executed", tasks.get(i));
+ for (int i = 0; i < tasks.size() && !stopRequested; i++) {
+ LOG.debug("Found notification task {} to be executed:
starting...", tasks.get(i));
+ executeSingle(tasks.get(i), executor);
+ LOG.debug("Notification task {} executed", tasks.get(i));
+ }
}
- }
- protected boolean hasToBeRegistered(final TaskExec<NotificationTask>
execution) {
- NotificationTask task = execution.getTask();
+ if (stopRequested) {
+ LOG.debug("Notification job interrupted");
+ }
+
+ setStatus(null);
+ }
+ protected boolean hasToBeRegistered(final NotificationTask task, final
TaskExec<NotificationTask> execution) {
// True if either failed and failures have to be registered, or if ALL
// has to be registered.
return (NotificationJob.Status.valueOf(execution.getStatus()) ==
NotificationJob.Status.NOT_SENT
@@ -193,7 +215,7 @@ public abstract class AbstractNotificationJobDelegate
implements NotificationJob
|| task.getTraceLevel() == TraceLevel.ALL;
}
- protected void handleRetries(final TaskExec<NotificationTask> execution) {
+ protected void handleRetries(final NotificationTask task, final
TaskExec<NotificationTask> execution) {
long maxRetries = confParamOps.get(SyncopeConstants.MASTER_DOMAIN,
"notification.maxRetries", 0L, Long.class);
if (maxRetries <= 0) {
@@ -201,12 +223,12 @@ public abstract class AbstractNotificationJobDelegate
implements NotificationJob
}
long failedExecutionsCount =
notificationManager.countExecutionsWithStatus(
- execution.getTask().getKey(),
NotificationJob.Status.NOT_SENT.name());
+ task.getKey(), NotificationJob.Status.NOT_SENT.name());
if (failedExecutionsCount <= maxRetries) {
LOG.debug("Execution of notification task {} will be retried
[{}/{}]",
- execution.getTask(), failedExecutionsCount, maxRetries);
- notificationManager.setTaskExecuted(execution.getTask().getKey(),
false);
+ task, failedExecutionsCount, maxRetries);
+ notificationManager.setTaskExecuted(task.getKey(), false);
auditManager.audit(
AuthContextUtils.getDomain(),
@@ -219,9 +241,9 @@ public abstract class AbstractNotificationJobDelegate
implements NotificationJob
null,
null,
execution,
- "Notification task " + execution.getTask().getKey() + "
will be retried");
+ "Notification task " + task.getKey() + " will be retried");
} else {
- LOG.error("Maximum number of retries reached for task {} - giving
up", execution.getTask());
+ LOG.error("Maximum number of retries reached for task {} - giving
up", task);
auditManager.audit(
AuthContextUtils.getDomain(),
@@ -234,7 +256,7 @@ public abstract class AbstractNotificationJobDelegate
implements NotificationJob
null,
null,
execution,
- "Giving up retries on notification task " +
execution.getTask().getKey());
+ "Giving up retries on notification task " + task.getKey());
}
}
}
diff --git
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java
index 0a9c1539ad..6af79f1031 100644
---
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java
+++
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java
@@ -80,6 +80,7 @@ import
org.apache.syncope.core.spring.implementation.ImplementationManager;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Transactional(rollbackFor = { Throwable.class })
@@ -438,17 +439,22 @@ public class DefaultNotificationManager implements
NotificationManager {
return email;
}
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
- public TaskExec<NotificationTask> storeExec(final
TaskExec<NotificationTask> execution) {
- NotificationTask task = taskDAO.findById(TaskType.NOTIFICATION,
execution.getTask().getKey()).
+ public TaskExec<NotificationTask> storeExec(final String taskKey, final
TaskExec<NotificationTask> execution) {
+ NotificationTask task = taskDAO.findById(TaskType.NOTIFICATION,
taskKey).
map(NotificationTask.class::cast).
orElseThrow(() -> new NotFoundException("NotificationTask " +
execution.getTask().getKey()));
+ execution.setTask(task);
task.add(execution);
task.setExecuted(true);
taskDAO.save(task);
+
+ execution.setTask(null);
return execution;
}
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void setTaskExecuted(final String taskKey, final boolean executed) {
NotificationTask task = taskDAO.findById(TaskType.NOTIFICATION,
taskKey).
diff --git a/pom.xml b/pom.xml
index 79fcacab9d..b32151d1e0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -438,7 +438,7 @@ under the License.
<spring-boot.version>4.0.6</spring-boot.version>
<spring-cloud-version>5.0.1</spring-cloud-version>
- <hibernate.version>7.3.5.Final</hibernate.version>
+ <hibernate.version>7.3.6.Final</hibernate.version>
<jasypt.version>1.9.3</jasypt.version>
diff --git a/src/site/xdoc/security.xml b/src/site/xdoc/security.xml
index 54d278a5e5..edb143d2f1 100644
--- a/src/site/xdoc/security.xml
+++ b/src/site/xdoc/security.xml
@@ -36,6 +36,90 @@ under the License.
<p>If you want to report a vulnerability, please follow <a
href="https://www.apache.org/security/">the procedure</a>.</p>
+ <subsection name="CVE-2026-42797: Apache Syncope: JexlContextBuilder
Information Disclosure">
+ <p>Exposure of Sensitive Information Through Data Queries
vulnerability in Apache Syncope.</p>
+ <p>An administrator with adequate entitlements for Derived Schemas can
create a malicious JEXL expression which allows any administrator with
sufficient entitlements for User read to access User-related security-sensitive
information.</p>
+
+ <p>
+ <b>Severity</b>
+ </p>
+ <p>Moderate</p>
+
+ <p>
+ <b>Affects</b>
+ </p>
+ <p>
+ <ul>
+ <li>4.1 through 4.1.0</li>
+ <li>4.0 through 4.0.5</li>
+ <li>3.0 through 3.0.16</li>
+ </ul>
+ </p>
+
+ <p>
+ <b>Solution</b>
+ </p>
+ <p>
+ <ul>
+ <li>Users are recommended to upgrade to version 4.1.1 / 4.0.6
which fix this issue.</li>
+ </ul>
+ </p>
+
+ <p>
+ <b>Fixed in</b>
+ </p>
+ <p>
+ <ul>
+ <li>Release 4.1.1</li>
+ <li>Release 4.0.6</li>
+ </ul>
+ </p>
+
+ <p>Read the <a
href="https://www.cve.org/CVERecord?id=CVE-2026-42797">full CVE
advisory</a>.</p>
+ </subsection>
+
+ <subsection name="CVE-2026-42782: Apache Syncope: Post-auth RCE via
Groovy static">
+ <p>Improper Isolation or Compartmentalization vulnerability in Apache
Syncope.</p>
+ <p>An administrator with adequate entitlements for Implementations can
create a malicious Groovy class containing untrusted code reaching a
non-sandboxed execution path via the class static initializer.</p>
+
+ <p>
+ <b>Severity</b>
+ </p>
+ <p>Moderate</p>
+
+ <p>
+ <b>Affects</b>
+ </p>
+ <p>
+ <ul>
+ <li>4.1 through 4.1.0</li>
+ <li>4.0 through 4.0.5</li>
+ <li>3.0 through 3.0.16</li>
+ </ul>
+ </p>
+
+ <p>
+ <b>Solution</b>
+ </p>
+ <p>
+ <ul>
+ <li>Users are recommended to upgrade to version 4.1.1 / 4.0.6
which fix this issue.</li>
+ </ul>
+ </p>
+
+ <p>
+ <b>Fixed in</b>
+ </p>
+ <p>
+ <ul>
+ <li>Release 4.1.1</li>
+ <li>Release 4.0.6</li>
+ </ul>
+ </p>
+
+ <p>Read the <a
href="https://www.cve.org/CVERecord?id=CVE-2026-42782">full CVE
advisory</a>.</p>
+ </subsection>
+
<subsection name="CVE-2026-23795: Apache Syncope: Console XXE on
Keymaster parameters">
<p>Improper Restriction of XML External Entity Reference vulnerability
in Apache Syncope Console.</p>
<p>An administrator with adequate entitlements to create or edit
Keymaster parameters via Console can construct malicious XML text to launch an
XXE attack, thereby causing sensitive data leakage occurs.</p>