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 45da018d58 [SYNCOPE-1786] Allowing domain selection for Keymaster API 
via Swagger UI + fixing KeymasterConfParamLoader for additional domains
45da018d58 is described below

commit 45da018d58f2672e15c7f508df91488b2f431434
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Wed Mar 13 10:49:50 2024 +0100

    [SYNCOPE-1786] Allowing domain selection for Keymaster API via Swagger UI + 
fixing KeymasterConfParamLoader for additional domains
---
 .../common/keymaster/client/api/DomainOps.java     |  2 +
 .../common/keymaster/client/api/model/Domain.java  | 10 +++
 .../client/zookeeper/ZookeeperDomainOps.java       | 82 +++++++++++++---------
 .../client/self/SelfKeymasterDomainOps.java        | 11 +++
 .../keymaster/rest/api/service/DomainService.java  | 12 ++++
 .../apache/syncope/core/logic/DummyDomainOps.java  |  5 ++
 .../apache/syncope/core/logic/DummyDomainOps.java  |  5 ++
 .../persistence/common/RuntimeDomainLoader.java    | 16 +++--
 .../common/content/KeymasterConfParamLoader.java   | 61 ++++++++--------
 .../persistence/jpa/JPARuntimeDomainLoader.java    |  4 +-
 .../core/persistence/jpa/PersistenceContext.java   |  3 +-
 .../core/persistence/jpa/DummyDomainOps.java       |  5 ++
 .../core/persistence/neo4j/PersistenceContext.java |  3 +-
 .../core/persistence/neo4j/DummyDomainOps.java     |  5 ++
 .../core/provisioning/java/DummyDomainOps.java     |  5 ++
 .../internal/SelfKeymasterInternalDomainOps.java   |  8 +++
 .../rest/cxf/service/DomainServiceImpl.java        |  5 ++
 .../org/apache/syncope/core/logic/DomainLogic.java | 11 +++
 .../syncope/core/starter/SelfKeymasterContext.java | 33 +++++++++
 .../syncope/core/starter/SyncopeCoreStart.java     |  3 +-
 .../syncope/core/workflow/java/DummyDomainOps.java |  5 ++
 .../apache/syncope/fit/core/KeymasterITCase.java   | 36 ++++------
 22 files changed, 233 insertions(+), 97 deletions(-)

diff --git 
a/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/DomainOps.java
 
b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/DomainOps.java
index b7ec18a9b5..78139f6cd6 100644
--- 
a/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/DomainOps.java
+++ 
b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/DomainOps.java
@@ -33,6 +33,8 @@ public interface DomainOps {
 
     void create(Domain domain);
 
+    void deployed(String key);
+
     void changeAdminPassword(String key, String password, CipherAlgorithm 
cipherAlgorithm);
 
     void adjustPoolSize(String key, int poolMaxActive, int poolMinIdle);
diff --git 
a/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/model/Domain.java
 
b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/model/Domain.java
index eec05b4b6f..6edb517586 100644
--- 
a/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/model/Domain.java
+++ 
b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/model/Domain.java
@@ -98,6 +98,8 @@ public abstract class Domain implements Serializable {
 
     protected String keymasterConfParams;
 
+    protected boolean deployed = false;
+
     @JsonProperty("_class")
     public String getDiscriminator() {
         return getClass().getName();
@@ -145,6 +147,14 @@ public abstract class Domain implements Serializable {
         return keymasterConfParams;
     }
 
+    public boolean isDeployed() {
+        return deployed;
+    }
+
+    public void setDeployed(final boolean deployed) {
+        this.deployed = deployed;
+    }
+
     @Override
     public int hashCode() {
         return new HashCodeBuilder().
diff --git 
a/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeeper/ZookeeperDomainOps.java
 
b/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeeper/ZookeeperDomainOps.java
index bc15acba26..f6d212d81c 100644
--- 
a/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeeper/ZookeeperDomainOps.java
+++ 
b/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeeper/ZookeeperDomainOps.java
@@ -67,41 +67,43 @@ public class ZookeeperDomainOps implements DomainOps, 
InitializingBean {
 
     @Override
     public void afterPropertiesSet() throws Exception {
-        if (watcher != null) {
-            if (client.checkExists().forPath(buildDomainPath()) == null) {
-                
client.create().creatingParentContainersIfNeeded().forPath(buildDomainPath());
-            }
+        if (watcher == null) {
+            LOG.warn("No watcher found, aborting");
+            return;
+        }
 
-            CuratorCache cache = CuratorCache.build(client, buildDomainPath());
-            cache.listenable().addListener((type, oldData, newData) -> {
-                switch (type) {
-                    case NODE_CREATED:
-                        LOG.debug("Domain {} added", newData.getPath());
-                        try {
-                            Domain domain = 
MAPPER.readValue(newData.getData(), Domain.class);
-
-                            LOG.info("Domain {} created", domain.getKey());
-                            watcher.added(domain);
-                        } catch (IOException e) {
-                            LOG.debug("Could not parse {}", new 
String(newData.getData()), e);
-                        }
-                        break;
-
-                    case NODE_CHANGED:
-                        LOG.debug("Domain {} updated", newData.getPath());
-                        break;
-
-                    case NODE_DELETED:
-                        LOG.debug("Domain {} removed", newData.getPath());
-                        
watcher.removed(StringUtils.substringAfter(newData.getPath(), DOMAIN_PATH + 
'/'));
-                        break;
-
-                    default:
-                        LOG.debug("Event {} received with data {}", type, 
newData);
-                }
-            });
-            cache.start();
+        if (client.checkExists().forPath(buildDomainPath()) == null) {
+            
client.create().creatingParentContainersIfNeeded().forPath(buildDomainPath());
         }
+
+        CuratorCache cache = CuratorCache.build(client, buildDomainPath());
+        cache.listenable().addListener((type, oldData, newData) -> {
+            switch (type) {
+                case NODE_CREATED -> {
+                    LOG.debug("Domain {} added", newData.getPath());
+                    try {
+                        Domain domain = MAPPER.readValue(newData.getData(), 
Domain.class);
+
+                        LOG.info("Domain {} created", domain.getKey());
+                        watcher.added(domain);
+                    } catch (IOException e) {
+                        LOG.debug("Could not parse {}", new 
String(newData.getData()), e);
+                    }
+                }
+
+                case NODE_CHANGED ->
+                    LOG.debug("Domain {} updated", newData.getPath());
+
+                case NODE_DELETED -> {
+                    LOG.debug("Domain {} removed", newData.getPath());
+                    
watcher.removed(StringUtils.substringAfter(newData.getPath(), DOMAIN_PATH + 
'/'));
+                }
+
+                default ->
+                    LOG.debug("Event {} received with data {}", type, newData);
+            }
+        });
+        cache.start();
     }
 
     @Override
@@ -151,6 +153,20 @@ public class ZookeeperDomainOps implements DomainOps, 
InitializingBean {
         }
     }
 
+    @Override
+    public void deployed(final String key) {
+        try {
+            Domain domain = read(key);
+
+            domain.setDeployed(true);
+            client.setData().forPath(buildDomainPath(key), 
MAPPER.writeValueAsBytes(domain));
+        } catch (KeymasterException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new KeymasterException(e);
+        }
+    }
+
     @Override
     public void changeAdminPassword(
             final String key, final String password, final CipherAlgorithm 
cipherAlgorithm) {
diff --git 
a/common/keymaster/self/client-self/src/main/java/org/apache/syncope/common/keymaster/client/self/SelfKeymasterDomainOps.java
 
b/common/keymaster/self/client-self/src/main/java/org/apache/syncope/common/keymaster/client/self/SelfKeymasterDomainOps.java
index 92a2dd4793..06a7c160a5 100644
--- 
a/common/keymaster/self/client-self/src/main/java/org/apache/syncope/common/keymaster/client/self/SelfKeymasterDomainOps.java
+++ 
b/common/keymaster/self/client-self/src/main/java/org/apache/syncope/common/keymaster/client/self/SelfKeymasterDomainOps.java
@@ -60,6 +60,17 @@ public class SelfKeymasterDomainOps extends SelfKeymasterOps 
implements DomainOp
         }
     }
 
+    @Override
+    public void deployed(final String key) {
+        try {
+            client(DomainService.class, Map.of()).deployed(key);
+        } catch (KeymasterException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new KeymasterException(e);
+        }
+    }
+
     @Override
     public void changeAdminPassword(final String key, final String password, 
final CipherAlgorithm cipherAlgorithm) {
         try {
diff --git 
a/common/keymaster/self/rest-api/src/main/java/org/apache/syncope/common/keymaster/rest/api/service/DomainService.java
 
b/common/keymaster/self/rest-api/src/main/java/org/apache/syncope/common/keymaster/rest/api/service/DomainService.java
index 489158e124..99a60102c3 100644
--- 
a/common/keymaster/self/rest-api/src/main/java/org/apache/syncope/common/keymaster/rest/api/service/DomainService.java
+++ 
b/common/keymaster/self/rest-api/src/main/java/org/apache/syncope/common/keymaster/rest/api/service/DomainService.java
@@ -92,6 +92,18 @@ public interface DomainService extends Serializable {
     @Produces({ MediaType.APPLICATION_JSON })
     Response create(Domain domain);
 
+    /**
+     * Notify that the given domain is deployed.
+     *
+     * @param key key of domain to be updated
+     */
+    @ApiResponses(
+            @ApiResponse(responseCode = "204", description = "Operation was 
successful"))
+    @POST
+    @Path("{key}/deployed")
+    @Produces({ MediaType.APPLICATION_JSON })
+    void deployed(@NotNull @PathParam("key") String key);
+
     /**
      * Change admin's password for the given domain.
      *
diff --git 
a/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyDomainOps.java
 
b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyDomainOps.java
index f899baea17..2af29d9bfb 100644
--- 
a/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyDomainOps.java
+++ 
b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyDomainOps.java
@@ -48,6 +48,11 @@ public class DummyDomainOps implements DomainOps {
         domainRegistry.register((JPADomain) domain);
     }
 
+    @Override
+    public void deployed(final String key) {
+        // nothing to do
+    }
+
     @Override
     public void changeAdminPassword(final String key, final String password, 
final CipherAlgorithm cipherAlgorithm) {
         // nothing to do
diff --git 
a/core/idrepo/logic/src/test/java/org/apache/syncope/core/logic/DummyDomainOps.java
 
b/core/idrepo/logic/src/test/java/org/apache/syncope/core/logic/DummyDomainOps.java
index f899baea17..2af29d9bfb 100644
--- 
a/core/idrepo/logic/src/test/java/org/apache/syncope/core/logic/DummyDomainOps.java
+++ 
b/core/idrepo/logic/src/test/java/org/apache/syncope/core/logic/DummyDomainOps.java
@@ -48,6 +48,11 @@ public class DummyDomainOps implements DomainOps {
         domainRegistry.register((JPADomain) domain);
     }
 
+    @Override
+    public void deployed(final String key) {
+        // nothing to do
+    }
+
     @Override
     public void changeAdminPassword(final String key, final String password, 
final CipherAlgorithm cipherAlgorithm) {
         // nothing to do
diff --git 
a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/RuntimeDomainLoader.java
 
b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/RuntimeDomainLoader.java
index 1d725f2938..278602178f 100644
--- 
a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/RuntimeDomainLoader.java
+++ 
b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/RuntimeDomainLoader.java
@@ -19,17 +19,18 @@
 package org.apache.syncope.core.persistence.common;
 
 import java.util.Comparator;
+import org.apache.syncope.common.keymaster.client.api.DomainOps;
 import org.apache.syncope.common.keymaster.client.api.DomainWatcher;
 import org.apache.syncope.common.keymaster.client.api.model.Domain;
 import org.apache.syncope.core.persistence.api.DomainHolder;
 import org.apache.syncope.core.persistence.api.DomainRegistry;
 import org.apache.syncope.core.persistence.api.SyncopeCoreLoader;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
-import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.aop.support.AopUtils;
 import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.scheduling.annotation.Async;
 
 public class RuntimeDomainLoader<D extends Domain> implements DomainWatcher {
 
@@ -39,13 +40,17 @@ public class RuntimeDomainLoader<D extends Domain> 
implements DomainWatcher {
 
     protected final DomainRegistry<D> domainRegistry;
 
+    protected final DomainOps domainOps;
+
     public RuntimeDomainLoader(
             final DomainHolder<?> domainHolder,
             final DomainRegistry<D> domainRegistry,
+            final DomainOps domainOps,
             final ConfigurableApplicationContext ctx) {
 
         this.domainHolder = domainHolder;
         this.domainRegistry = domainRegistry;
+        this.domainOps = domainOps;
 
         // only needed by ZookeeperDomainOps' early init on afterPropertiesSet
         if (ApplicationContextProvider.getApplicationContext() == null) {
@@ -57,6 +62,7 @@ public class RuntimeDomainLoader<D extends Domain> implements 
DomainWatcher {
         // nothing to do
     }
 
+    @Async
     @SuppressWarnings("unchecked")
     @Override
     public void added(final Domain domain) {
@@ -76,17 +82,17 @@ public class RuntimeDomainLoader<D extends Domain> 
implements DomainWatcher {
 
                         LOG.debug("[{}] Starting on domain '{}'", loaderName, 
domain);
 
-                        AuthContextUtils.runAsAdmin(
-                                domain.getKey(),
-                                () -> loader.load(domain.getKey()));
+                        loader.load(domain.getKey());
 
                         LOG.debug("[{}] Completed on domain '{}'", loaderName, 
domain);
                     });
 
+            domainOps.deployed(domain.getKey());
             LOG.info("Domain {} successfully deployed", domain.getKey());
         }
     }
 
+    @Async
     @Override
     public void removed(final String domain) {
         if (domainHolder.getDomains().containsKey(domain)) {
@@ -98,7 +104,9 @@ public class RuntimeDomainLoader<D extends Domain> 
implements DomainWatcher {
                         String loaderName = 
AopUtils.getTargetClass(loader).getName();
 
                         LOG.debug("[{}] Starting dispose on domain '{}'", 
loaderName, domain);
+
                         loader.unload(domain);
+
                         LOG.debug("[{}] Dispose completed on domain '{}'", 
loaderName, domain);
                     });
 
diff --git 
a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/content/KeymasterConfParamLoader.java
 
b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/content/KeymasterConfParamLoader.java
index 6cd7754d48..13a96801ea 100644
--- 
a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/content/KeymasterConfParamLoader.java
+++ 
b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/content/KeymasterConfParamLoader.java
@@ -20,15 +20,17 @@ package org.apache.syncope.core.persistence.common.content;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.json.JsonMapper;
-import java.io.IOException;
 import java.io.InputStream;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Optional;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
 import org.apache.syncope.core.persistence.api.content.ConfParamLoader;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.transaction.annotation.Transactional;
 
 /**
  * Initialize Keymaster with default content if no data is present already.
@@ -52,41 +54,38 @@ public class KeymasterConfParamLoader implements 
ConfParamLoader {
 
     @Override
     public void load(final String domain) {
-        boolean existingData;
-        try {
-            existingData = !confParamOps.list(domain).isEmpty();
-        } catch (Exception e) {
-            LOG.error("[{}] Could not access Keymaster", domain, e);
-            existingData = true;
-        }
+        AuthContextUtils.runAsAdmin(domain, new Runnable() {
 
-        if (existingData) {
-            LOG.info("[{}] Data found in Keymaster, leaving untouched", 
domain);
-        } else {
-            LOG.info("[{}] Empty Keymaster found, loading default content", 
domain);
+            @Transactional
+            @Override
+            public void run() {
+                boolean existingData;
+                try {
+                    existingData = !confParamOps.list(domain).isEmpty();
+                } catch (Exception e) {
+                    LOG.error("[{}] Could not access Keymaster", domain, e);
+                    existingData = true;
+                }
 
-            try {
-                InputStream contentJSON = 
ApplicationContextProvider.getBeanFactory().
-                        getBean(domain + "KeymasterConfParamsJSON", 
InputStream.class);
-                loadDefaultContent(domain, contentJSON);
-            } catch (Exception e) {
-                LOG.error("[{}] While loading default Keymaster content", 
domain, e);
-            }
-        }
-    }
+                if (existingData) {
+                    LOG.info("[{}] Data found in Keymaster, leaving 
untouched", domain);
+                } else {
+                    LOG.info("[{}] Empty Keymaster found, loading default 
content", domain);
 
-    protected void loadDefaultContent(final String domain, final InputStream 
contentJSON)
-            throws IOException {
+                    try (InputStream contentJSON = 
ApplicationContextProvider.getBeanFactory().
+                            getBean(domain + "KeymasterConfParamsJSON", 
InputStream.class)) {
 
-        try (contentJSON) {
-            JsonNode content = MAPPER.readTree(contentJSON);
-            for (Iterator<Map.Entry<String, JsonNode>> itor = 
content.fields(); itor.hasNext();) {
-                Map.Entry<String, JsonNode> param = itor.next();
-                Object value = MAPPER.treeToValue(param.getValue(), 
Object.class);
-                if (value != null) {
-                    confParamOps.set(domain, param.getKey(), value);
+                        JsonNode content = MAPPER.readTree(contentJSON);
+                        for (Iterator<Map.Entry<String, JsonNode>> itor = 
content.fields(); itor.hasNext();) {
+                            Map.Entry<String, JsonNode> param = itor.next();
+                            
Optional.ofNullable(MAPPER.treeToValue(param.getValue(), Object.class)).
+                                    ifPresent(value -> 
confParamOps.set(domain, param.getKey(), value));
+                        }
+                    } catch (Exception e) {
+                        LOG.error("[{}] While loading default Keymaster 
content", domain, e);
+                    }
                 }
             }
-        }
+        });
     }
 }
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/JPARuntimeDomainLoader.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/JPARuntimeDomainLoader.java
index 5ad94dc8c5..54cd86b90d 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/JPARuntimeDomainLoader.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/JPARuntimeDomainLoader.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.persistence.jpa;
 
+import org.apache.syncope.common.keymaster.client.api.DomainOps;
 import org.apache.syncope.common.keymaster.client.api.model.Domain;
 import org.apache.syncope.common.keymaster.client.api.model.JPADomain;
 import org.apache.syncope.core.persistence.api.DomainHolder;
@@ -35,9 +36,10 @@ public class JPARuntimeDomainLoader extends 
RuntimeDomainLoader<JPADomain> {
             final DomainHolder<?> domainHolder,
             final DomainRegistry<JPADomain> domainRegistry,
             final DomainRoutingEntityManagerFactory entityManagerFactory,
+            final DomainOps domainOps,
             final ConfigurableApplicationContext ctx) {
 
-        super(domainHolder, domainRegistry, ctx);
+        super(domainHolder, domainRegistry, domainOps, ctx);
         this.entityManagerFactory = entityManagerFactory;
     }
 
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
index 619b10f8aa..4b088db279 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
@@ -331,9 +331,10 @@ public class PersistenceContext {
             final DomainHolder<?> domainHolder,
             final DomainRegistry<JPADomain> domainRegistry,
             final DomainRoutingEntityManagerFactory entityManagerFactory,
+            final @Lazy DomainOps domainOps,
             final ConfigurableApplicationContext ctx) {
 
-        return new JPARuntimeDomainLoader(domainHolder, domainRegistry, 
entityManagerFactory, ctx);
+        return new JPARuntimeDomainLoader(domainHolder, domainRegistry, 
entityManagerFactory, domainOps, ctx);
     }
 
     @ConditionalOnMissingBean
diff --git 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyDomainOps.java
 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyDomainOps.java
index 8491534651..8cae9f1ee9 100644
--- 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyDomainOps.java
+++ 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyDomainOps.java
@@ -48,6 +48,11 @@ public class DummyDomainOps implements DomainOps {
         domainRegistry.register((JPADomain) domain);
     }
 
+    @Override
+    public void deployed(final String key) {
+        // nothing to do
+    }
+
     @Override
     public void changeAdminPassword(final String key, final String password, 
final CipherAlgorithm cipherAlgorithm) {
         // nothing to do
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/PersistenceContext.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/PersistenceContext.java
index 3d06e5efb1..ed1854cfcd 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/PersistenceContext.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/PersistenceContext.java
@@ -372,9 +372,10 @@ public class PersistenceContext {
     public RuntimeDomainLoader<Neo4jDomain> runtimeDomainLoader(
             final DomainHolder<?> domainHolder,
             final DomainRegistry<Neo4jDomain> domainRegistry,
+            final @Lazy DomainOps domainOps,
             final ConfigurableApplicationContext ctx) {
 
-        return new RuntimeDomainLoader<>(domainHolder, domainRegistry, ctx);
+        return new RuntimeDomainLoader<>(domainHolder, domainRegistry, 
domainOps, ctx);
     }
 
     @ConditionalOnMissingBean
diff --git 
a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/DummyDomainOps.java
 
b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/DummyDomainOps.java
index 2a0c39d4c6..f80bff7b36 100644
--- 
a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/DummyDomainOps.java
+++ 
b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/DummyDomainOps.java
@@ -48,6 +48,11 @@ public class DummyDomainOps implements DomainOps {
         domainRegistry.register((Neo4jDomain) domain);
     }
 
+    @Override
+    public void deployed(final String key) {
+        // nothing to do
+    }
+
     @Override
     public void changeAdminPassword(final String key, final String password, 
final CipherAlgorithm cipherAlgorithm) {
         // nothing to do
diff --git 
a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyDomainOps.java
 
b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyDomainOps.java
index bd29f19659..bd5fb03bad 100644
--- 
a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyDomainOps.java
+++ 
b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyDomainOps.java
@@ -48,6 +48,11 @@ public class DummyDomainOps implements DomainOps {
         domainRegistry.register((JPADomain) domain);
     }
 
+    @Override
+    public void deployed(final String key) {
+        // nothing to do
+    }
+
     @Override
     public void changeAdminPassword(final String key, final String password, 
final CipherAlgorithm cipherAlgorithm) {
         // nothing to do
diff --git 
a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/keymaster/internal/SelfKeymasterInternalDomainOps.java
 
b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/keymaster/internal/SelfKeymasterInternalDomainOps.java
index ca74494bd7..27882487df 100644
--- 
a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/keymaster/internal/SelfKeymasterInternalDomainOps.java
+++ 
b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/keymaster/internal/SelfKeymasterInternalDomainOps.java
@@ -64,6 +64,14 @@ public class SelfKeymasterInternalDomainOps implements 
DomainOps {
         });
     }
 
+    @Override
+    public void deployed(final String key) {
+        AuthContextUtils.callAs(SyncopeConstants.MASTER_DOMAIN, 
props.getUsername(), List.of(), () -> {
+            logic.deployed(key);
+            return null;
+        });
+    }
+
     @Override
     public void changeAdminPassword(final String key, final String password, 
final CipherAlgorithm cipherAlgorithm) {
         AuthContextUtils.callAs(SyncopeConstants.MASTER_DOMAIN, 
props.getUsername(), List.of(), () -> {
diff --git 
a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/keymaster/rest/cxf/service/DomainServiceImpl.java
 
b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/keymaster/rest/cxf/service/DomainServiceImpl.java
index ec94bca8d3..f5c81cdb8b 100644
--- 
a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/keymaster/rest/cxf/service/DomainServiceImpl.java
+++ 
b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/keymaster/rest/cxf/service/DomainServiceImpl.java
@@ -63,6 +63,11 @@ public class DomainServiceImpl implements DomainService {
                 build();
     }
 
+    @Override
+    public void deployed(final String key) {
+        logic.deployed(key);
+    }
+
     @Override
     public void changeAdminPassword(final String key, final String password, 
final CipherAlgorithm cipherAlgorithm) {
         logic.changeAdminPassword(key, password, cipherAlgorithm);
diff --git 
a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/logic/DomainLogic.java
 
b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/logic/DomainLogic.java
index 832cee9dd7..f5c6a54717 100644
--- 
a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/logic/DomainLogic.java
+++ 
b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/logic/DomainLogic.java
@@ -87,6 +87,17 @@ public class DomainLogic extends 
AbstractTransactionalLogic<EntityTO> {
         return domainEntity.get();
     }
 
+    @PreAuthorize("@environment.getProperty('keymaster.username') == 
authentication.name")
+    public void deployed(final String key) {
+        DomainEntity domain = domainDAO.findById(key).
+                orElseThrow(() -> new NotFoundException("Domain " + key));
+
+        Domain domainObj = domain.get();
+        domainObj.setDeployed(true);
+        domain.set(domainObj);
+        domainDAO.save(domain);
+    }
+
     @PreAuthorize("@environment.getProperty('keymaster.username') == 
authentication.name")
     public void changeAdminPassword(final String key, final String password, 
final CipherAlgorithm cipherAlgorithm) {
         DomainEntity domain = domainDAO.findById(key).
diff --git 
a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/starter/SelfKeymasterContext.java
 
b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/starter/SelfKeymasterContext.java
index 9f23bb4d12..29353ff024 100644
--- 
a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/starter/SelfKeymasterContext.java
+++ 
b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/starter/SelfKeymasterContext.java
@@ -20,9 +20,14 @@ package org.apache.syncope.core.starter;
 
 import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider;
 import io.swagger.v3.oas.integration.api.OpenAPIConfiguration;
+import io.swagger.v3.oas.models.ExternalDocumentation;
+import io.swagger.v3.oas.models.media.Schema;
+import io.swagger.v3.oas.models.parameters.HeaderParameter;
+import io.swagger.v3.oas.models.parameters.Parameter;
 import io.swagger.v3.oas.models.security.SecurityScheme;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.regex.Pattern;
 import org.apache.commons.lang3.StringUtils;
@@ -45,6 +50,8 @@ import 
org.apache.syncope.common.keymaster.client.api.ServiceOps;
 import org.apache.syncope.common.keymaster.rest.api.service.ConfParamService;
 import org.apache.syncope.common.keymaster.rest.api.service.DomainService;
 import 
org.apache.syncope.common.keymaster.rest.api.service.NetworkServiceService;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.core.keymaster.internal.InternalConfParamHelper;
 import 
org.apache.syncope.core.keymaster.internal.SelfKeymasterInternalConfParamOps;
 import 
org.apache.syncope.core.keymaster.internal.SelfKeymasterInternalDomainOps;
@@ -56,6 +63,7 @@ import 
org.apache.syncope.core.keymaster.rest.security.SelfKeymasterUsernamePass
 import org.apache.syncope.core.logic.ConfParamLogic;
 import org.apache.syncope.core.logic.DomainLogic;
 import org.apache.syncope.core.logic.NetworkServiceLogic;
+import org.apache.syncope.core.persistence.api.DomainHolder;
 import org.apache.syncope.core.persistence.api.dao.keymaster.ConfParamDAO;
 import org.apache.syncope.core.persistence.api.dao.keymaster.DomainDAO;
 import org.apache.syncope.core.persistence.api.dao.keymaster.NetworkServiceDAO;
@@ -105,6 +113,7 @@ public class SelfKeymasterContext {
 
     @Bean
     public Server selfKeymasterContainer(
+            final DomainHolder<?> domainHolder,
             final ConfParamService confParamService,
             final NetworkServiceService networkServiceService,
             final DomainService domainService,
@@ -158,6 +167,30 @@ public class SelfKeymasterContext {
 
                 return configuration;
             }
+
+            @Override
+            protected void addParameters(final List<Parameter> parameters) {
+                Optional<Parameter> domainHeaderParameter = 
parameters.stream().
+                        filter(p -> p instanceof HeaderParameter && 
RESTHeaders.DOMAIN.equals(p.getName())).findFirst();
+                if (domainHeaderParameter.isEmpty()) {
+                    HeaderParameter parameter = new HeaderParameter();
+                    parameter.setName(RESTHeaders.DOMAIN);
+                    parameter.setRequired(true);
+
+                    ExternalDocumentation extDoc = new ExternalDocumentation();
+                    extDoc.setDescription("Apache Syncope Reference Guide");
+                    
extDoc.setUrl("https://syncope.apache.org/docs/3.0/reference-guide.html#domains";);
+
+                    Schema<String> schema = new Schema<>();
+                    schema.setDescription("Domains are built to facilitate 
multitenancy.");
+                    schema.setExternalDocs(extDoc);
+                    
schema.setEnum(domainHolder.getDomains().keySet().stream().sorted().toList());
+                    schema.setDefault(SyncopeConstants.MASTER_DOMAIN);
+                    parameter.setSchema(schema);
+
+                    parameters.add(parameter);
+                }
+            }
         };
         openApiCustomizer.setDynamicBasePath(false);
         openApiCustomizer.setReplaceTags(false);
diff --git 
a/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStart.java
 
b/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStart.java
index d9d48e7d78..b3e6e440e1 100644
--- 
a/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStart.java
+++ 
b/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStart.java
@@ -24,7 +24,6 @@ import 
org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart;
 import org.apache.syncope.core.persistence.api.DomainHolder;
 import org.apache.syncope.core.persistence.api.SyncopeCoreLoader;
 import org.apache.syncope.core.provisioning.java.job.JobStatusUpdater;
-import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.aop.support.AopUtils;
@@ -64,7 +63,7 @@ public class SyncopeCoreStart extends KeymasterStart 
implements Ordered {
                     domainHolder.getDomains().keySet().forEach(domain -> {
                         LOG.debug("[{}] Starting init on domain '{}'", 
loaderName, domain);
 
-                        AuthContextUtils.runAsAdmin(domain, () -> 
loader.load(domain));
+                        loader.load(domain);
 
                         LOG.debug("[{}] Init completed on domain '{}'", 
loaderName, domain);
                     });
diff --git 
a/core/workflow-java/src/test/java/org/apache/syncope/core/workflow/java/DummyDomainOps.java
 
b/core/workflow-java/src/test/java/org/apache/syncope/core/workflow/java/DummyDomainOps.java
index 54f4f1338e..b5edf5ec50 100644
--- 
a/core/workflow-java/src/test/java/org/apache/syncope/core/workflow/java/DummyDomainOps.java
+++ 
b/core/workflow-java/src/test/java/org/apache/syncope/core/workflow/java/DummyDomainOps.java
@@ -48,6 +48,11 @@ public class DummyDomainOps implements DomainOps {
         domainRegistry.register((JPADomain) domain);
     }
 
+    @Override
+    public void deployed(final String key) {
+        // nothing to do
+    }
+
     @Override
     public void changeAdminPassword(final String key, final String password, 
final CipherAlgorithm cipherAlgorithm) {
         // nothing to do
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/KeymasterITCase.java
 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/KeymasterITCase.java
index 3df305e6ca..3f7e2cb593 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/KeymasterITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/KeymasterITCase.java
@@ -44,7 +44,6 @@ import 
org.apache.syncope.common.keymaster.client.api.KeymasterException;
 import org.apache.syncope.common.keymaster.client.api.model.Domain;
 import org.apache.syncope.common.keymaster.client.api.model.JPADomain;
 import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
-import org.apache.syncope.common.keymaster.client.self.SelfKeymasterDomainOps;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.request.UserCR;
 import org.apache.syncope.common.lib.to.PagedResult;
@@ -216,11 +215,11 @@ public class KeymasterITCase extends AbstractITCase {
         assumeTrue(initial.stream().anyMatch(domain -> 
"Two".equals(domain.getKey())));
 
         // 1. create new domain
-        String key = UUID.randomUUID().toString();
+        String newDomain = UUID.randomUUID().toString();
 
-        domainOps.create(new JPADomain.Builder(key).
+        domainOps.create(new JPADomain.Builder(newDomain).
                 jdbcDriver("org.h2.Driver").
-                jdbcURL("jdbc:h2:mem:syncopetest" + key + 
";DB_CLOSE_DELAY=-1").
+                jdbcURL("jdbc:h2:mem:syncopetest" + newDomain + 
";DB_CLOSE_DELAY=-1").
                 dbUsername("sa").
                 dbPassword("").
                 databasePlatform("org.apache.openjpa.jdbc.sql.H2Dictionary").
@@ -229,37 +228,26 @@ public class KeymasterITCase extends AbstractITCase {
                 adminCipherAlgorithm(CipherAlgorithm.BCRYPT).
                 build());
 
-        JPADomain domain = (JPADomain) domainOps.read(key);
+        JPADomain domain = (JPADomain) domainOps.read(newDomain);
         
assertEquals(JPADomain.TransactionIsolation.TRANSACTION_READ_UNCOMMITTED, 
domain.getTransactionIsolation());
         assertEquals(CipherAlgorithm.BCRYPT, domain.getAdminCipherAlgorithm());
         assertEquals(10, domain.getPoolMaxActive());
         assertEquals(2, domain.getPoolMinIdle());
 
-        assertEquals(domain, domainOps.read(key));
+        assertEquals(domain, domainOps.read(newDomain));
 
         // 2. update domain
-        domainOps.adjustPoolSize(key, 100, 23);
+        domainOps.adjustPoolSize(newDomain, 100, 23);
 
-        domain = (JPADomain) domainOps.read(key);
+        domain = (JPADomain) domainOps.read(newDomain);
         assertEquals(100, domain.getPoolMaxActive());
         assertEquals(23, domain.getPoolMinIdle());
 
-        // temporarily finish test case at this point in case Zookeeper
-        // is used: in such a case, in fact, errors are found in the logs
-        // at this point as follows:
-        // org.springframework.beans.factory.BeanCreationException: Error 
creating bean
-        // with name
-        // 
'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration':
-        // Initialization of bean failed; nested exception is
-        // org.springframework.beans.factory.NoSuchBeanDefinitionException: No 
bean named
-        // 
'org.springframework.context.annotation.ConfigurationClassPostProcessor.importRegistry'
-        // available
-        // the same test, execute alone, works fine with Zookeeper, so it musy 
be something
-        // set or left unclean from previous tests
-        assumeTrue(domainOps instanceof SelfKeymasterDomainOps);
-
         // 3. work with new domain - create user
-        CLIENT_FACTORY = new 
SyncopeClientFactoryBean().setAddress(ADDRESS).setDomain(key);
+        await().atMost(MAX_WAIT_SECONDS, TimeUnit.SECONDS).pollInterval(1, 
TimeUnit.SECONDS).
+                until(() -> domainOps.read(newDomain).isDeployed());
+
+        CLIENT_FACTORY = new 
SyncopeClientFactoryBean().setAddress(ADDRESS).setDomain(newDomain);
         ADMIN_CLIENT = CLIENT_FACTORY.create(ADMIN_UNAME, "password");
 
         USER_SERVICE = ADMIN_CLIENT.getService(UserService.class);
@@ -297,7 +285,7 @@ public class KeymasterITCase extends AbstractITCase {
         assertEquals(1, users.getTotalCount());
 
         // 4. delete domain
-        domainOps.delete(key);
+        domainOps.delete(newDomain);
 
         List<Domain> list = domainOps.list();
         assertEquals(initial, list);


Reply via email to