This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch 4_0_X
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/4_0_X by this push:
     new 59909ffeb7 [SYNCOPE-1919] Ensuring that JobStatusUpdater can only 
update existing rows (#1199)
59909ffeb7 is described below

commit 59909ffeb7d0b783fd7b292902c43631d36f8d38
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Wed Oct 1 10:33:24 2025 +0200

    [SYNCOPE-1919] Ensuring that JobStatusUpdater can only update existing rows 
(#1199)
---
 .../syncope/core/logic/AbstractJobLogic.java       |  3 +-
 .../core/persistence/api/dao/JobStatusDAO.java     | 10 ++-
 .../core/persistence/jpa/dao/JPAJobStatusDAO.java  | 86 ++++++++--------------
 .../persistence/neo4j/dao/Neo4jJobStatusDAO.java   | 64 +++++-----------
 .../provisioning/java/ProvisioningContext.java     |  4 +-
 .../provisioning/java/job/JobStatusUpdater.java    | 20 +----
 .../java/job/JobStatusUpdaterTest.java             | 19 +++--
 .../core/src/main/resources/core-oracle.properties |  2 -
 .../docker-compose/docker-compose-oracle.yml       |  4 +-
 9 files changed, 76 insertions(+), 136 deletions(-)

diff --git 
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java
 
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java
index e32b9b0c43..7b3b33c1dd 100644
--- 
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java
+++ 
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AbstractJobLogic.java
@@ -28,7 +28,6 @@ import org.apache.syncope.common.lib.to.JobTO;
 import org.apache.syncope.common.lib.types.JobAction;
 import org.apache.syncope.common.lib.types.JobType;
 import org.apache.syncope.core.persistence.api.dao.JobStatusDAO;
-import org.apache.syncope.core.persistence.api.entity.JobStatus;
 import org.apache.syncope.core.provisioning.api.job.JobManager;
 import org.apache.syncope.core.provisioning.java.job.SyncopeTaskScheduler;
 import org.apache.syncope.core.provisioning.java.job.SystemLoadReporterJob;
@@ -91,7 +90,7 @@ abstract class AbstractJobLogic<T extends EntityTO> extends 
AbstractTransactiona
 
             jobTO.setRunning(jobManager.isRunning(jobName));
 
-            
jobTO.setStatus(jobStatusDAO.findById(jobName).map(JobStatus::getStatus).orElse("UNKNOWN"));
+            jobTO.setStatus(jobStatusDAO.get(jobName));
         }
 
         return Optional.ofNullable(jobTO);
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/JobStatusDAO.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/JobStatusDAO.java
index 40114748d7..0fa09e7aa8 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/JobStatusDAO.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/JobStatusDAO.java
@@ -18,13 +18,17 @@
  */
 package org.apache.syncope.core.persistence.api.dao;
 
-import org.apache.syncope.core.persistence.api.entity.JobStatus;
-
-public interface JobStatusDAO extends DAO<JobStatus> {
+public interface JobStatusDAO {
 
     String JOB_FIRED_STATUS = "JOB_FIRED";
 
+    String UNKNOWN_STATUS = "UNKNOWN";
+
     boolean lock(String key);
 
     void unlock(String key);
+
+    void set(String key, String status);
+
+    String get(String key);
 }
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAJobStatusDAO.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAJobStatusDAO.java
index 761347e301..520808b525 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAJobStatusDAO.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAJobStatusDAO.java
@@ -20,11 +20,8 @@ package org.apache.syncope.core.persistence.jpa.dao;
 
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.Query;
-import jakarta.persistence.TypedQuery;
 import java.util.List;
-import java.util.Optional;
 import org.apache.syncope.core.persistence.api.dao.JobStatusDAO;
-import org.apache.syncope.core.persistence.api.entity.JobStatus;
 import org.apache.syncope.core.persistence.jpa.entity.JPAJobStatus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -41,64 +38,21 @@ public class JPAJobStatusDAO implements JobStatusDAO {
         this.entityManager = entityManager;
     }
 
-    @Transactional(readOnly = true)
-    @Override
-    public boolean existsById(final String key) {
-        Query query = entityManager.createQuery(
-                "SELECT COUNT(e) FROM " + JPAJobStatus.class.getSimpleName() + 
" e WHERE e.id = :key");
-        query.setParameter("key", key);
-        return ((Number) query.getSingleResult()).longValue() > 0;
-    }
-
-    @Transactional(readOnly = true)
-    @Override
-    public Optional<? extends JobStatus> findById(final String key) {
-        return Optional.ofNullable(entityManager.find(JPAJobStatus.class, 
key));
-    }
-
-    @Transactional(readOnly = true)
-    @Override
-    public long count() {
-        Query query = entityManager.createQuery(
-                "SELECT COUNT(e) FROM " + JPAJobStatus.class.getSimpleName() + 
" e");
-        return ((Number) query.getSingleResult()).longValue();
-    }
-
-    @Transactional(readOnly = true)
-    @Override
-    public List<? extends JobStatus> findAll() {
-        TypedQuery<JobStatus> query = entityManager.createQuery(
-                "SELECT e FROM " + JPAJobStatus.class.getSimpleName() + " e", 
JobStatus.class);
-        return query.getResultList();
-    }
-
-    @Override
-    public <S extends JobStatus> S save(final S jobStatus) {
-        return entityManager.merge(jobStatus);
-    }
-
-    @Override
-    public void delete(final JobStatus jobStatus) {
-        entityManager.remove(jobStatus);
-    }
-
-    @Override
-    public void deleteById(final String key) {
-        findById(key).ifPresent(this::delete);
-    }
-
     @Override
     public boolean lock(final String key) {
-        if (existsById(key)) {
+        if (!UNKNOWN_STATUS.equals(get(key))) {
+            LOG.debug("Job {} already locked", key);
             return false;
         }
 
         try {
-            JobStatus jobStatus = new JPAJobStatus();
-            jobStatus.setKey(key);
-            jobStatus.setStatus(JOB_FIRED_STATUS);
-            save(jobStatus);
+            Query query = entityManager.createNativeQuery(
+                    "INSERT INTO " + JPAJobStatus.TABLE + "(id, jobStatus) 
VALUES (?,?)");
+            query.setParameter(1, key);
+            query.setParameter(2, JOB_FIRED_STATUS);
+            query.executeUpdate();
 
+            LOG.debug("Job {} locked", key);
             return true;
         } catch (Exception e) {
             LOG.debug("Could not lock job {}", key, e);
@@ -108,6 +62,28 @@ public class JPAJobStatusDAO implements JobStatusDAO {
 
     @Override
     public void unlock(final String key) {
-        deleteById(key);
+        Query query = entityManager.createNativeQuery("DELETE FROM " + 
JPAJobStatus.TABLE + " WHERE id=?");
+        query.setParameter(1, key);
+        query.executeUpdate();
+        LOG.debug("Job {} unlocked", key);
+    }
+
+    @Override
+    public void set(final String key, final String status) {
+        Query query = entityManager.createNativeQuery("UPDATE " + 
JPAJobStatus.TABLE + " SET jobStatus=? WHERE id=?");
+        query.setParameter(1, UNKNOWN_STATUS.equals(status) ? "Status " + 
UNKNOWN_STATUS : status);
+        query.setParameter(2, key);
+        query.executeUpdate();
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public String get(final String key) {
+        Query query = entityManager.createNativeQuery("SELECT jobStatus FROM " 
+ JPAJobStatus.TABLE + " WHERE id=?");
+        query.setParameter(1, key);
+
+        @SuppressWarnings("unchecked")
+        List<Object> result = query.getResultList();
+        return result.isEmpty() ? UNKNOWN_STATUS : 
result.getFirst().toString();
     }
 }
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jJobStatusDAO.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jJobStatusDAO.java
index 62d7948183..f283de6529 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jJobStatusDAO.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jJobStatusDAO.java
@@ -18,8 +18,6 @@
  */
 package org.apache.syncope.core.persistence.neo4j.dao;
 
-import java.util.List;
-import java.util.Optional;
 import org.apache.syncope.core.persistence.api.dao.JobStatusDAO;
 import org.apache.syncope.core.persistence.api.entity.JobStatus;
 import org.apache.syncope.core.persistence.neo4j.entity.Neo4jJobStatus;
@@ -43,48 +41,10 @@ public class Neo4jJobStatusDAO implements JobStatusDAO {
         this.nodeValidator = nodeValidator;
     }
 
-    @Transactional(readOnly = true)
-    @Override
-    public boolean existsById(final String key) {
-        return neo4jTemplate.existsById(key, Neo4jJobStatus.class);
-    }
-
-    @Transactional(readOnly = true)
-    @Override
-    public Optional<? extends JobStatus> findById(final String key) {
-        return neo4jTemplate.findById(key, Neo4jJobStatus.class);
-    }
-
-    @Transactional(readOnly = true)
-    @Override
-    public long count() {
-        return neo4jTemplate.count(Neo4jJobStatus.class);
-    }
-
-    @Transactional(readOnly = true)
-    @Override
-    public List<? extends JobStatus> findAll() {
-        return neo4jTemplate.findAll(Neo4jJobStatus.class);
-    }
-
-    @Override
-    public <S extends JobStatus> S save(final S jobStatus) {
-        return neo4jTemplate.save(nodeValidator.validate(jobStatus));
-    }
-
-    @Override
-    public void delete(final JobStatus jobStatus) {
-        neo4jTemplate.deleteById(jobStatus.getKey(), Neo4jJobStatus.class);
-    }
-
-    @Override
-    public void deleteById(final String key) {
-        findById(key).ifPresent(this::delete);
-    }
-
     @Override
     public boolean lock(final String key) {
-        if (existsById(key)) {
+        if (neo4jTemplate.existsById(key, Neo4jJobStatus.class)) {
+            LOG.debug("Job {} already locked", key);
             return false;
         }
 
@@ -92,7 +52,7 @@ public class Neo4jJobStatusDAO implements JobStatusDAO {
             JobStatus jobStatus = new Neo4jJobStatus();
             jobStatus.setKey(key);
             jobStatus.setStatus(JOB_FIRED_STATUS);
-            save(jobStatus);
+            neo4jTemplate.save(nodeValidator.validate(jobStatus));
 
             return true;
         } catch (Exception e) {
@@ -103,6 +63,22 @@ public class Neo4jJobStatusDAO implements JobStatusDAO {
 
     @Override
     public void unlock(final String key) {
-        deleteById(key);
+        neo4jTemplate.deleteById(key, Neo4jJobStatus.class);
+    }
+
+    @Override
+    public void set(final String key, final String status) {
+        if (neo4jTemplate.existsById(key, Neo4jJobStatus.class)) {
+            JobStatus jobStatus = new Neo4jJobStatus();
+            jobStatus.setKey(key);
+            jobStatus.setStatus(status);
+            neo4jTemplate.save(nodeValidator.validate(jobStatus));
+        }
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public String get(final String key) {
+        return neo4jTemplate.findById(key, 
Neo4jJobStatus.class).map(JobStatus::getStatus).orElse(UNKNOWN_STATUS);
     }
 }
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
index 88a3d2fcad..d16b7d349b 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
@@ -291,8 +291,8 @@ public class ProvisioningContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public JobStatusUpdater jobStatusUpdater(final JobStatusDAO jobStatusDAO, 
final EntityFactory entityFactory) {
-        return new JobStatusUpdater(jobStatusDAO, entityFactory);
+    public JobStatusUpdater jobStatusUpdater(final JobStatusDAO jobStatusDAO) {
+        return new JobStatusUpdater(jobStatusDAO);
     }
 
     @ConditionalOnMissingBean
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/JobStatusUpdater.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/JobStatusUpdater.java
index e1668fe935..24e81d1f66 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/JobStatusUpdater.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/JobStatusUpdater.java
@@ -19,8 +19,6 @@
 package org.apache.syncope.core.provisioning.java.job;
 
 import org.apache.syncope.core.persistence.api.dao.JobStatusDAO;
-import org.apache.syncope.core.persistence.api.entity.EntityFactory;
-import org.apache.syncope.core.persistence.api.entity.JobStatus;
 import org.apache.syncope.core.provisioning.api.event.JobStatusEvent;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.slf4j.Logger;
@@ -34,13 +32,10 @@ public class JobStatusUpdater {
 
     protected final JobStatusDAO jobStatusDAO;
 
-    protected final EntityFactory entityFactory;
-
     protected boolean initCompleted = false;
 
-    public JobStatusUpdater(final JobStatusDAO jobStatusDAO, final 
EntityFactory entityFactory) {
+    public JobStatusUpdater(final JobStatusDAO jobStatusDAO) {
         this.jobStatusDAO = jobStatusDAO;
-        this.entityFactory = entityFactory;
     }
 
     public void initComplete() {
@@ -69,16 +64,9 @@ public class JobStatusUpdater {
             LOG.debug("Updating job '{}#{}' with status '{}'",
                     event.getDomain(), event.getJobName(), 
event.getJobStatus());
 
-            AuthContextUtils.runAsAdmin(event.getDomain(), () -> {
-                JobStatus jobStatus = 
jobStatusDAO.findById(event.getJobName()).orElse(null);
-                if (jobStatus == null) {
-                    jobStatus = entityFactory.newEntity(JobStatus.class);
-                    jobStatus.setKey(event.getJobName());
-                }
-
-                jobStatus.setStatus(event.getJobStatus());
-                jobStatusDAO.save(jobStatus);
-            });
+            AuthContextUtils.runAsAdmin(
+                    event.getDomain(),
+                    () -> jobStatusDAO.set(event.getJobName(), 
event.getJobStatus()));
         }
     }
 }
diff --git 
a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/job/JobStatusUpdaterTest.java
 
b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/job/JobStatusUpdaterTest.java
index bdf7078d55..807ad300c7 100644
--- 
a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/job/JobStatusUpdaterTest.java
+++ 
b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/job/JobStatusUpdaterTest.java
@@ -18,11 +18,10 @@
  */
 package org.apache.syncope.core.provisioning.java.job;
 
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.core.persistence.api.dao.JobStatusDAO;
-import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.provisioning.api.event.JobStatusEvent;
 import org.apache.syncope.core.provisioning.java.AbstractTest;
 import org.apache.syncope.core.spring.security.SecureRandomUtils;
@@ -33,9 +32,6 @@ import 
org.springframework.transaction.annotation.Transactional;
 @Transactional
 public class JobStatusUpdaterTest extends AbstractTest {
 
-    @Autowired
-    private EntityFactory entityFactory;
-
     @Autowired
     private JobStatusDAO jobStatusDAO;
 
@@ -43,14 +39,17 @@ public class JobStatusUpdaterTest extends AbstractTest {
     public void verifyUpdate() {
         String jobName = "job-" + SecureRandomUtils.generateRandomNumber();
 
-        JobStatusUpdater jobStatusUpdater = new JobStatusUpdater(jobStatusDAO, 
entityFactory);
+        JobStatusUpdater jobStatusUpdater = new JobStatusUpdater(jobStatusDAO);
         jobStatusUpdater.initComplete();
 
         jobStatusUpdater.update(new JobStatusEvent(this, 
SyncopeConstants.MASTER_DOMAIN, jobName, "Started"));
-        assertTrue(jobStatusDAO.findById(jobName).isPresent());
+        assertEquals(JobStatusDAO.UNKNOWN_STATUS, jobStatusDAO.get(jobName));
+
+        jobStatusDAO.lock(jobName);
+        jobStatusUpdater.update(new JobStatusEvent(this, 
SyncopeConstants.MASTER_DOMAIN, jobName, "Started"));
+        assertEquals("Started", jobStatusDAO.get(jobName));
 
-        jobStatusUpdater.update(new JobStatusEvent(this, 
SyncopeConstants.MASTER_DOMAIN, jobName, null));
-        // no change is expected
-        assertTrue(jobStatusDAO.findById(jobName).isPresent());
+        jobStatusDAO.unlock(jobName);
+        assertEquals(JobStatusDAO.UNKNOWN_STATUS, jobStatusDAO.get(jobName));
     }
 }
diff --git a/docker/core/src/main/resources/core-oracle.properties 
b/docker/core/src/main/resources/core-oracle.properties
index b92ee09975..1df1c6e56e 100644
--- a/docker/core/src/main/resources/core-oracle.properties
+++ b/docker/core/src/main/resources/core-oracle.properties
@@ -28,5 +28,3 @@ 
persistence.domain[0].databasePlatform=org.apache.openjpa.jdbc.sql.OracleDiction
 persistence.domain[0].orm=META-INF/oracle/spring-orm.xml
 persistence.domain[0].poolMaxActive=${DB_POOL_MAX}
 persistence.domain[0].poolMinIdle=${DB_POOL_MIN}
-
-persistence.indexesXML=classpath:oracle_indexes.xml
diff --git a/docker/src/main/resources/docker-compose/docker-compose-oracle.yml 
b/docker/src/main/resources/docker-compose/docker-compose-oracle.yml
index 5954cc31c8..cf67fecd90 100644
--- a/docker/src/main/resources/docker-compose/docker-compose-oracle.yml
+++ b/docker/src/main/resources/docker-compose/docker-compose-oracle.yml
@@ -29,14 +29,14 @@ services:
    syncope:
      depends_on:
        - db
-     command: ["wait-for-it", "db:3306", "-t", "60", "--", 
"/opt/syncope/bin/startup.sh"]
+     command: ["wait-for-it", "db:1521", "-t", "60", "--", 
"/opt/syncope/bin/startup.sh"]
      image: apache/syncope:${SYNCOPE_VERSION}
      ports:
        - "18080:8080"
      restart: always
      environment:
        SPRING_PROFILES_ACTIVE: docker,oracle,saml2
-       DB_URL: jdbc:oracle:thin:@db:1521/XEPDB1
+       DB_URL: jdbc:oracle:thin:@db:1521/FREEPDB1
        DB_SCHEMA: SYNCOPE
        DB_USER: syncope
        DB_PASSWORD: syncope

Reply via email to