You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2022/03/30 18:38:35 UTC

[syncope] branch master updated: [SYNCOPE-1670] Enable for graceful shutdown (#332)

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 553d9c6  [SYNCOPE-1670] Enable for graceful shutdown (#332)
553d9c6 is described below

commit 553d9c6618bf844eba80135c202abd188f207431
Author: Francesco Chicchiriccò <il...@users.noreply.github.com>
AuthorDate: Wed Mar 30 20:38:29 2022 +0200

    [SYNCOPE-1670] Enable for graceful shutdown (#332)
---
 .../console/src/main/resources/console.properties  |   3 +
 .../enduser/src/main/resources/enduser.properties  |   3 +
 .../client/api/startstop/KeymasterStart.java       |   3 +-
 .../client/api/startstop/KeymasterStop.java        |   3 +-
 .../syncope/core/logic/IdRepoLogicContext.java     | 147 ++++++++++++---------
 .../core/rest/cxf/IdRepoRESTCXFContext.java        |  25 +++-
 .../syncope/core/rest/cxf/RESTProperties.java}     |  25 ++--
 .../core/persistence/api/SyncopeCoreLoader.java    |  13 +-
 .../syncope/core/persistence/jpa/MasterDomain.java |  34 +++--
 .../core/persistence/jpa/PersistenceContext.java   |   6 +-
 .../core/persistence/jpa/RuntimeDomainLoader.java  |  18 ++-
 core/provisioning-api/pom.xml                      |  31 ++---
 .../core/provisioning/java/ExecutorProperties.java |  62 +++++++++
 .../provisioning/java/ProvisioningContext.java     |  54 ++++----
 .../provisioning/java/ProvisioningProperties.java  |  43 ++----
 .../core/starter/SyncopeCoreApplication.java       |  52 +++++---
 .../syncope/core/starter/SyncopeCoreStart.java     |  10 +-
 ...{SyncopeCoreStart.java => SyncopeCoreStop.java} |  30 ++---
 .../syncope/core/starter/TaskExecutorUnloader.java |  64 +++++++++
 core/starter/src/main/resources/core.properties    |   3 +
 .../core/flowable/support/DomainProcessEngine.java |   2 +-
 .../support/DomainProcessEngineFactoryBean.java    |   9 +-
 .../src/main/resources/core-embedded.properties    |   1 +
 pom.xml                                            |  34 ++---
 sra/src/main/resources/sra.properties              |   3 +
 wa/starter/src/main/resources/wa.properties        |   3 +
 26 files changed, 422 insertions(+), 259 deletions(-)

diff --git a/client/idrepo/console/src/main/resources/console.properties b/client/idrepo/console/src/main/resources/console.properties
index 51a37c6..489bdd6 100644
--- a/client/idrepo/console/src/main/resources/console.properties
+++ b/client/idrepo/console/src/main/resources/console.properties
@@ -30,6 +30,9 @@ management.endpoint.health.show-details=ALWAYS
 
 service.discovery.address=http://localhost:8080/syncope-console/
 
+server.shutdown=graceful
+spring.lifecycle.timeout-per-shutdown-phase=30s
+
 wicket.core.csrf.enabled=false
 
 logging.config=classpath:/log4j2.xml
diff --git a/client/idrepo/enduser/src/main/resources/enduser.properties b/client/idrepo/enduser/src/main/resources/enduser.properties
index c9d8cc6..ff0bd6a 100644
--- a/client/idrepo/enduser/src/main/resources/enduser.properties
+++ b/client/idrepo/enduser/src/main/resources/enduser.properties
@@ -30,6 +30,9 @@ management.endpoint.health.show-details=ALWAYS
 
 service.discovery.address=http://localhost:8080/syncope-enduser/
 
+server.shutdown=graceful
+spring.lifecycle.timeout-per-shutdown-phase=30s
+
 wicket.core.csrf.enabled=false
 
 logging.config=classpath:/log4j2.xml
diff --git a/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStart.java b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStart.java
index f164865..abbf86d 100644
--- a/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStart.java
+++ b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStart.java
@@ -22,8 +22,7 @@ import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.springframework.context.ApplicationListener;
 import org.springframework.context.event.ContextRefreshedEvent;
 
-public class KeymasterStart extends KeymasterStartStop
-        implements ApplicationListener<ContextRefreshedEvent> {
+public class KeymasterStart extends KeymasterStartStop implements ApplicationListener<ContextRefreshedEvent> {
 
     public KeymasterStart(final NetworkService.Type networkServiceType) {
         super(networkServiceType);
diff --git a/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStop.java b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStop.java
index 7851a39..8127056 100644
--- a/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStop.java
+++ b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStop.java
@@ -22,8 +22,7 @@ import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.springframework.context.ApplicationListener;
 import org.springframework.context.event.ContextClosedEvent;
 
-public class KeymasterStop extends KeymasterStartStop
-        implements ApplicationListener<ContextClosedEvent> {
+public class KeymasterStop extends KeymasterStartStop implements ApplicationListener<ContextClosedEvent> {
 
     public KeymasterStop(final NetworkService.Type networkServiceType) {
         super(networkServiceType);
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java
index f28af9a..bbb7d88 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java
@@ -106,8 +106,10 @@ public class IdRepoLogicContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public LogicInvocationHandler logicInvocationHandler(final NotificationManager notificationManager,
-                                                         final AuditManager auditManager) {
+    public LogicInvocationHandler logicInvocationHandler(
+            final NotificationManager notificationManager,
+            final AuditManager auditManager) {
+
         return new LogicInvocationHandler(notificationManager, auditManager);
     }
 
@@ -125,8 +127,11 @@ public class IdRepoLogicContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public AuditLoader auditLoader(final AuditAccessor auditAccessor, final ImplementationLookup implementationLookup,
-                                   final LogicProperties logicProperties) {
+    public AuditLoader auditLoader(
+            final AuditAccessor auditAccessor,
+            final ImplementationLookup implementationLookup,
+            final LogicProperties logicProperties) {
+
         return new AuditLoader(auditAccessor, implementationLookup, logicProperties);
     }
 
@@ -150,9 +155,11 @@ public class IdRepoLogicContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public AccessTokenLogic accessTokenLogic(final AccessTokenDataBinder binder,
-                                             final AccessTokenDAO accessTokenDAO,
-                                             final SecurityProperties securityProperties) {
+    public AccessTokenLogic accessTokenLogic(
+            final AccessTokenDataBinder binder,
+            final AccessTokenDAO accessTokenDAO,
+            final SecurityProperties securityProperties) {
+
         return new AccessTokenLogic(securityProperties, binder, accessTokenDAO);
     }
 
@@ -179,16 +186,20 @@ public class IdRepoLogicContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public AnyTypeClassLogic anyTypeClassLogic(final AnyTypeClassDataBinder binder,
-                                               final AnyTypeClassDAO anyTypeClassDAO) {
+    public AnyTypeClassLogic anyTypeClassLogic(
+            final AnyTypeClassDataBinder binder,
+            final AnyTypeClassDAO anyTypeClassDAO) {
+
         return new AnyTypeClassLogic(binder, anyTypeClassDAO);
     }
 
     @ConditionalOnMissingBean
     @Bean
-    public AnyTypeLogic anyTypeLogic(final AnyTypeDataBinder binder,
-                                     final AnyTypeDAO anyTypeDAO,
-                                     final AnyObjectDAO anyObjectDAO) {
+    public AnyTypeLogic anyTypeLogic(
+            final AnyTypeDataBinder binder,
+            final AnyTypeDAO anyTypeDAO,
+            final AnyObjectDAO anyObjectDAO) {
+
         return new AnyTypeLogic(binder, anyTypeDAO, anyObjectDAO);
     }
 
@@ -222,9 +233,11 @@ public class IdRepoLogicContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public DelegationLogic delegationLogic(final DelegationDataBinder binder,
-                                           final UserDAO userDAO,
-                                           final DelegationDAO delegationDAO) {
+    public DelegationLogic delegationLogic(
+            final DelegationDataBinder binder,
+            final UserDAO userDAO,
+            final DelegationDAO delegationDAO) {
+
         return new DelegationLogic(binder, delegationDAO, userDAO);
     }
 
@@ -239,22 +252,24 @@ public class IdRepoLogicContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public GroupLogic groupLogic(final GroupProvisioningManager provisioningManager,
-                                 final JobManager jobManager,
-                                 final TemplateUtils templateUtils,
-                                 final EntityFactory entityFactory,
-                                 final RealmDAO realmDAO,
-                                 final AnyTypeDAO anyTypeDAO,
-                                 final UserDAO userDAO,
-                                 final GroupDAO groupDAO,
-                                 final AnySearchDAO anySearchDAO,
-                                 final SchedulerFactoryBean scheduler,
-                                 final TaskDAO taskDAO,
-                                 final ConfParamOps confParamOps,
-                                 final GroupDataBinder groupDataBinder,
-                                 final TaskDataBinder taskDataBinder,
-                                 final ImplementationDAO implementationDAO,
-                                 final SecurityProperties securityProperties) {
+    public GroupLogic groupLogic(
+            final GroupProvisioningManager provisioningManager,
+            final JobManager jobManager,
+            final TemplateUtils templateUtils,
+            final EntityFactory entityFactory,
+            final RealmDAO realmDAO,
+            final AnyTypeDAO anyTypeDAO,
+            final UserDAO userDAO,
+            final GroupDAO groupDAO,
+            final AnySearchDAO anySearchDAO,
+            final SchedulerFactoryBean scheduler,
+            final TaskDAO taskDAO,
+            final ConfParamOps confParamOps,
+            final GroupDataBinder groupDataBinder,
+            final TaskDataBinder taskDataBinder,
+            final ImplementationDAO implementationDAO,
+            final SecurityProperties securityProperties) {
+
         return new GroupLogic(
                 realmDAO,
                 anyTypeDAO,
@@ -276,15 +291,17 @@ public class IdRepoLogicContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public ImplementationLogic implementationLogic(final ImplementationDataBinder binder,
-                                                   final PlainSchemaDAO plainSchemaDAO,
-                                                   final RealmDAO realmDAO,
-                                                   final PolicyDAO policyDAO,
-                                                   final ReportDAO reportDAO,
-                                                   final TaskDAO taskDAO,
-                                                   final ExternalResourceDAO externalResourceDAO,
-                                                   final ImplementationDAO implementationDAO,
-                                                   final NotificationDAO notificationDAO) {
+    public ImplementationLogic implementationLogic(
+            final ImplementationDataBinder binder,
+            final PlainSchemaDAO plainSchemaDAO,
+            final RealmDAO realmDAO,
+            final PolicyDAO policyDAO,
+            final ReportDAO reportDAO,
+            final TaskDAO taskDAO,
+            final ExternalResourceDAO externalResourceDAO,
+            final ImplementationDAO implementationDAO,
+            final NotificationDAO notificationDAO) {
+
         return new ImplementationLogic(
                 binder,
                 implementationDAO,
@@ -299,18 +316,22 @@ public class IdRepoLogicContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public MailTemplateLogic mailTemplateLogic(final MailTemplateDAO mailTemplateDAO,
-                                               final EntityFactory entityFactory,
-                                               final NotificationDAO notificationDAO) {
+    public MailTemplateLogic mailTemplateLogic(
+            final MailTemplateDAO mailTemplateDAO,
+            final EntityFactory entityFactory,
+            final NotificationDAO notificationDAO) {
+
         return new MailTemplateLogic(mailTemplateDAO, notificationDAO, entityFactory);
     }
 
     @ConditionalOnMissingBean
     @Bean
-    public NotificationLogic notificationLogic(final NotificationDataBinder binder,
-                                               final JobManager jobManager,
-                                               final SchedulerFactoryBean scheduler,
-                                               final NotificationDAO notificationDAO) {
+    public NotificationLogic notificationLogic(
+            final NotificationDataBinder binder,
+            final JobManager jobManager,
+            final SchedulerFactoryBean scheduler,
+            final NotificationDAO notificationDAO) {
+
         return new NotificationLogic(jobManager, scheduler, notificationDAO, binder);
     }
 
@@ -326,11 +347,13 @@ public class IdRepoLogicContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public RealmLogic realmLogic(final RealmDataBinder binder,
-                                 final RealmDAO realmDAO,
-                                 final AnySearchDAO anySearchDAO,
-                                 final PropagationManager propagationManager,
-                                 final PropagationTaskExecutor taskExecutor) {
+    public RealmLogic realmLogic(
+            final RealmDataBinder binder,
+            final RealmDAO realmDAO,
+            final AnySearchDAO anySearchDAO,
+            final PropagationManager propagationManager,
+            final PropagationTaskExecutor taskExecutor) {
+
         return new RealmLogic(realmDAO, anySearchDAO, binder, propagationManager, taskExecutor);
     }
 
@@ -359,9 +382,11 @@ public class IdRepoLogicContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public ReportTemplateLogic reportTemplateLogic(final ReportTemplateDAO reportTemplateDAO,
-                                                   final ReportDAO reportDAO,
-                                                   final EntityFactory entityFactory) {
+    public ReportTemplateLogic reportTemplateLogic(
+            final ReportTemplateDAO reportTemplateDAO,
+            final ReportDAO reportDAO,
+            final EntityFactory entityFactory) {
+
         return new ReportTemplateLogic(reportTemplateDAO, reportDAO, entityFactory);
     }
 
@@ -376,11 +401,13 @@ public class IdRepoLogicContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public SchemaLogic schemaLogic(final SchemaDataBinder binder,
-                                   final VirSchemaDAO virSchemaDAO,
-                                   final AnyTypeClassDAO anyTypeClassDAO,
-                                   final DerSchemaDAO derSchemaDAO,
-                                   final PlainSchemaDAO plainSchemaDAO) {
+    public SchemaLogic schemaLogic(
+            final SchemaDataBinder binder,
+            final VirSchemaDAO virSchemaDAO,
+            final AnyTypeClassDAO anyTypeClassDAO,
+            final DerSchemaDAO derSchemaDAO,
+            final PlainSchemaDAO plainSchemaDAO) {
+
         return new SchemaLogic(plainSchemaDAO, derSchemaDAO, virSchemaDAO, anyTypeClassDAO, binder);
     }
 
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdRepoRESTCXFContext.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdRepoRESTCXFContext.java
index 6d28e97..cb7df87 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdRepoRESTCXFContext.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdRepoRESTCXFContext.java
@@ -26,6 +26,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ThreadPoolExecutor;
 import javax.servlet.ServletRequestListener;
 import org.apache.cxf.Bus;
 import org.apache.cxf.endpoint.Server;
@@ -124,6 +125,7 @@ import org.apache.syncope.core.rest.cxf.service.TaskServiceImpl;
 import org.apache.syncope.core.rest.cxf.service.UserSelfServiceImpl;
 import org.apache.syncope.core.rest.cxf.service.UserServiceImpl;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
@@ -133,17 +135,23 @@ import org.springframework.core.env.Environment;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
 @PropertySource("classpath:errorMessages.properties")
+@EnableConfigurationProperties(RESTProperties.class)
 @Configuration(proxyBeanMethods = false)
 public class IdRepoRESTCXFContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public ThreadPoolTaskExecutor batchExecutor() {
-        ThreadPoolTaskExecutor batchExecutor = new ThreadPoolTaskExecutor();
-        batchExecutor.setCorePoolSize(10);
-        batchExecutor.setThreadNamePrefix("Batch-");
-        batchExecutor.initialize();
-        return batchExecutor;
+    public ThreadPoolTaskExecutor batchExecutor(final RESTProperties props) {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        executor.setCorePoolSize(props.getBatchExecutor().getCorePoolSize());
+        executor.setMaxPoolSize(props.getBatchExecutor().getMaxPoolSize());
+        executor.setQueueCapacity(props.getBatchExecutor().getQueueCapacity());
+        executor.setAwaitTerminationSeconds(props.getBatchExecutor().getAwaitTerminationSeconds());
+        executor.setWaitForTasksToCompleteOnShutdown(true);
+        executor.setThreadNamePrefix("Batch-");
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
+        executor.initialize();
+        return executor;
     }
 
     @ConditionalOnMissingBean
@@ -475,8 +483,11 @@ public class IdRepoRESTCXFContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public UserService userService(final UserDAO userDAO, final UserLogic userLogic,
+    public UserService userService(
+            final UserDAO userDAO,
+            final UserLogic userLogic,
             final SearchCondVisitor searchCondVisitor) {
+
         return new UserServiceImpl(searchCondVisitor, userDAO, userLogic);
     }
 }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SchedulerShutdown.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/RESTProperties.java
similarity index 55%
rename from core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SchedulerShutdown.java
rename to core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/RESTProperties.java
index 04e6ddb..78176ee 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SchedulerShutdown.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/RESTProperties.java
@@ -16,26 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.java.job;
+package org.apache.syncope.core.rest.cxf;
 
-import org.springframework.beans.factory.DisposableBean;
-import org.springframework.context.ApplicationContext;
-import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+import org.apache.syncope.core.provisioning.java.ExecutorProperties;
+import org.springframework.boot.context.properties.ConfigurationProperties;
 
-/**
- * Clean shutdown for Quartz scheduler.
- */
-public class SchedulerShutdown implements DisposableBean {
-
-    private final ApplicationContext ctx;
+@ConfigurationProperties("rest")
+public class RESTProperties {
 
-    public SchedulerShutdown(final ApplicationContext ctx) {
-        this.ctx = ctx;
-    }
+    private final ExecutorProperties batchExecutor = new ExecutorProperties();
 
-    @Override
-    public void destroy() throws Exception {
-        SchedulerFactoryBean scheduler = ctx.getBean(SchedulerFactoryBean.class);
-        scheduler.getScheduler().shutdown();
+    public ExecutorProperties getBatchExecutor() {
+        return batchExecutor;
     }
 }
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/SyncopeCoreLoader.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/SyncopeCoreLoader.java
index 786d370..9f6f2b3 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/SyncopeCoreLoader.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/SyncopeCoreLoader.java
@@ -25,14 +25,14 @@ import org.springframework.core.Ordered;
 public interface SyncopeCoreLoader extends Ordered {
 
     /**
-     * Perform generic (not related to any domain) initialization operations.
+     * Perform generic (not related to any domain) init operations.
      */
     default void load() {
         // nothing to do
     }
 
     /**
-     * Perform initialization operations on the given domain.
+     * Perform init operations on the given domain.
      *
      * @param domain domain to initialize
      * @param datasource db access for the given domain
@@ -42,11 +42,18 @@ public interface SyncopeCoreLoader extends Ordered {
     }
 
     /**
-     * Perform closing operations on the given domain.
+     * Perform dispose operations on the given domain.
      *
      * @param domain domain to unload
      */
     default void unload(String domain) {
         // nothing to do        
     }
+
+    /**
+     * Perform generic (not related to any domain) dispose operations.
+     */
+    default void unload() {
+        // nothing to do
+    }
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/MasterDomain.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/MasterDomain.java
index 17593f9..e701849 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/MasterDomain.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/MasterDomain.java
@@ -24,7 +24,6 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.Map;
 import java.util.Objects;
-
 import javax.sql.DataSource;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.core.persistence.jpa.spring.CommonEntityManagerFactoryConf;
@@ -69,9 +68,10 @@ public class MasterDomain {
     @ConditionalOnMissingBean(name = "MasterDataSourceInitializer")
     @Bean(name = "MasterDataSourceInitializer")
     public DataSourceInitializer masterDataSourceInitializer(
-        final PersistenceProperties props,
-        @Qualifier("MasterDataSource")
-        final JndiObjectFactoryBean masterDataSource) {
+            final PersistenceProperties props,
+            @Qualifier("MasterDataSource")
+            final JndiObjectFactoryBean masterDataSource) {
+
         ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
         databasePopulator.setContinueOnError(true);
         databasePopulator.setIgnoreFailedDrops(true);
@@ -89,10 +89,11 @@ public class MasterDomain {
     @DependsOn("commonEMFConf")
     @Bean(name = "MasterEntityManagerFactory")
     public DomainEntityManagerFactoryBean masterEntityManagerFactory(
-        final PersistenceProperties props,
-        @Qualifier("MasterDataSource")
-        final JndiObjectFactoryBean masterDataSource,
-        final CommonEntityManagerFactoryConf commonEMFConf) {
+            final PersistenceProperties props,
+            @Qualifier("MasterDataSource")
+            final JndiObjectFactoryBean masterDataSource,
+            final CommonEntityManagerFactoryConf commonEMFConf) {
+
         OpenJpaVendorAdapter vendorAdapter = new OpenJpaVendorAdapter();
         vendorAdapter.setShowSql(false);
         vendorAdapter.setGenerateDdl(true);
@@ -117,20 +118,25 @@ public class MasterDomain {
     @ConditionalOnMissingBean(name = "MasterTransactionManager")
     @Bean(name = { "MasterTransactionManager", "Master" })
     public PlatformTransactionManager transactionManager(
-        @Qualifier("MasterEntityManagerFactory")
-        final DomainEntityManagerFactoryBean masterEntityManagerFactory) {
+            @Qualifier("MasterEntityManagerFactory")
+            final DomainEntityManagerFactoryBean masterEntityManagerFactory) {
+
         return new JpaTransactionManager(Objects.requireNonNull(masterEntityManagerFactory.getObject()));
     }
 
     @Bean(name = "MasterContentXML")
-    public InputStream masterContentXML(final ResourceLoader resourceLoader,
-                                        final PersistenceProperties props) throws IOException {
+    public InputStream masterContentXML(
+            final ResourceLoader resourceLoader,
+            final PersistenceProperties props) throws IOException {
+
         return resourceLoader.getResource(props.getDomain().get(0).getContent()).getInputStream();
     }
 
     @Bean(name = "MasterKeymasterConfParamsJSON")
-    public InputStream masterKeymasterConfParamsJSON(final ResourceLoader resourceLoader,
-                                                     final PersistenceProperties props) throws IOException {
+    public InputStream masterKeymasterConfParamsJSON(
+            final ResourceLoader resourceLoader,
+            final PersistenceProperties props) throws IOException {
+
         return resourceLoader.getResource(props.getDomain().get(0).getKeymasterConfParams()).getInputStream();
     }
 
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 81edd76..c38af10 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
@@ -136,6 +136,7 @@ import org.apache.syncope.core.persistence.jpa.spring.MultiJarAwarePersistenceUn
 import org.apache.syncope.core.spring.security.SecurityProperties;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.ListableBeanFactory;
 import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -237,9 +238,10 @@ public class PersistenceContext {
     @Bean
     public RuntimeDomainLoader runtimeDomainLoader(
             final DomainHolder domainHolder,
-            final DomainRegistry domainRegistry) {
+            final DomainRegistry domainRegistry,
+            final ListableBeanFactory beanFactory) {
 
-        return new RuntimeDomainLoader(domainHolder, domainRegistry);
+        return new RuntimeDomainLoader(domainHolder, domainRegistry, beanFactory);
     }
 
     @ConditionalOnMissingBean
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/RuntimeDomainLoader.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/RuntimeDomainLoader.java
index 1269a87..bd2f90b 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/RuntimeDomainLoader.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/RuntimeDomainLoader.java
@@ -28,6 +28,7 @@ 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.springframework.aop.support.AopUtils;
+import org.springframework.beans.factory.ListableBeanFactory;
 
 public class RuntimeDomainLoader implements DomainWatcher {
 
@@ -37,9 +38,16 @@ public class RuntimeDomainLoader implements DomainWatcher {
 
     protected final DomainRegistry domainRegistry;
 
-    public RuntimeDomainLoader(final DomainHolder domainHolder, final DomainRegistry domainRegistry) {
+    protected final ListableBeanFactory beanFactory;
+
+    public RuntimeDomainLoader(
+            final DomainHolder domainHolder,
+            final DomainRegistry domainRegistry,
+            final ListableBeanFactory beanFactory) {
+
         this.domainHolder = domainHolder;
         this.domainRegistry = domainRegistry;
+        this.beanFactory = beanFactory;
     }
 
     @Override
@@ -70,14 +78,14 @@ public class RuntimeDomainLoader implements DomainWatcher {
         if (domainHolder.getDomains().containsKey(domain)) {
             LOG.info("Domain {} unregistration", domain);
 
-            ApplicationContextProvider.getApplicationContext().getBeansOfType(SyncopeCoreLoader.class).values().
-                    stream().sorted(Comparator.comparing(SyncopeCoreLoader::getOrder).reversed()).
+            beanFactory.getBeansOfType(SyncopeCoreLoader.class).values().stream().
+                    sorted(Comparator.comparing(SyncopeCoreLoader::getOrder).reversed()).
                     forEachOrdered(loader -> {
                         String loaderName = AopUtils.getTargetClass(loader).getName();
 
-                        LOG.debug("[{}] Starting on domain '{}'", loaderName, domain);
+                        LOG.debug("[{}] Starting dispose on domain '{}'", loaderName, domain);
                         loader.unload(domain);
-                        LOG.debug("[{}] Completed on domain '{}'", loaderName, domain);
+                        LOG.debug("[{}] Dispose completed on domain '{}'", loaderName, domain);
                     });
 
             domainRegistry.unregister(domain);
diff --git a/core/provisioning-api/pom.xml b/core/provisioning-api/pom.xml
index 10cac1e..c412723 100644
--- a/core/provisioning-api/pom.xml
+++ b/core/provisioning-api/pom.xml
@@ -39,37 +39,28 @@ under the License.
 
   <dependencies>
     <dependency>
-      <groupId>org.apache.commons</groupId>
-      <artifactId>commons-jexl3</artifactId>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-quartz</artifactId>
     </dependency>
 
     <dependency>
-      <groupId>com.fasterxml.jackson.core</groupId>
-      <artifactId>jackson-core</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>com.fasterxml.jackson.module</groupId>
-      <artifactId>jackson-module-afterburner</artifactId>
+      <groupId>org.apache.syncope.core</groupId>
+      <artifactId>syncope-core-persistence-api</artifactId>
+      <version>${project.version}</version>
     </dependency>
 
     <dependency>
-      <groupId>org.springframework</groupId>
-      <artifactId>spring-context</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.springframework</groupId>
-      <artifactId>spring-tx</artifactId>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-jexl3</artifactId>
     </dependency>
 
     <dependency>
-      <groupId>org.quartz-scheduler</groupId>
-      <artifactId>quartz</artifactId>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-core</artifactId>
     </dependency>
-
     <dependency>
-      <groupId>org.apache.syncope.core</groupId>
-      <artifactId>syncope-core-persistence-api</artifactId>
-      <version>${project.version}</version>
+      <groupId>com.fasterxml.jackson.module</groupId>
+      <artifactId>jackson-module-afterburner</artifactId>
     </dependency>
 
     <dependency>
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ExecutorProperties.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ExecutorProperties.java
new file mode 100644
index 0000000..d01a5bb
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ExecutorProperties.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java;
+
+public class ExecutorProperties {
+
+    private int corePoolSize = 5;
+
+    private int maxPoolSize = 25;
+
+    private int queueCapacity = 100;
+
+    private int awaitTerminationSeconds = 5;
+
+    public int getCorePoolSize() {
+        return corePoolSize;
+    }
+
+    public void setCorePoolSize(final int corePoolSize) {
+        this.corePoolSize = corePoolSize;
+    }
+
+    public int getMaxPoolSize() {
+        return maxPoolSize;
+    }
+
+    public void setMaxPoolSize(final int maxPoolSize) {
+        this.maxPoolSize = maxPoolSize;
+    }
+
+    public int getQueueCapacity() {
+        return queueCapacity;
+    }
+
+    public void setQueueCapacity(final int queueCapacity) {
+        this.queueCapacity = queueCapacity;
+    }
+
+    public int getAwaitTerminationSeconds() {
+        return awaitTerminationSeconds;
+    }
+
+    public void setAwaitTerminationSeconds(final int awaitTerminationSeconds) {
+        this.awaitTerminationSeconds = awaitTerminationSeconds;
+    }
+}
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 03613fb..766ddb7 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
@@ -156,7 +156,6 @@ import org.apache.syncope.core.provisioning.java.data.WAConfigDataBinderImpl;
 import org.apache.syncope.core.provisioning.java.data.wa.WAClientAppDataBinderImpl;
 import org.apache.syncope.core.provisioning.java.job.DefaultJobManager;
 import org.apache.syncope.core.provisioning.java.job.SchedulerDBInit;
-import org.apache.syncope.core.provisioning.java.job.SchedulerShutdown;
 import org.apache.syncope.core.provisioning.java.job.SyncopeSpringBeanJobFactory;
 import org.apache.syncope.core.provisioning.java.job.SystemLoadReporterJob;
 import org.apache.syncope.core.provisioning.java.job.notification.DefaultNotificationJobDelegate;
@@ -220,17 +219,18 @@ public class ProvisioningContext {
     /**
      * Annotated as {@code @Primary} because it will be used by {@code @Async} in {@link AsyncConnectorFacade}.
      *
-     * @param provisioningProperties configuration properties
-     *
+     * @param props configuration properties
      * @return executor
      */
     @Bean
     @Primary
-    public ThreadPoolTaskExecutor asyncConnectorFacadeExecutor(final ProvisioningProperties provisioningProperties) {
+    public ThreadPoolTaskExecutor asyncConnectorFacadeExecutor(final ProvisioningProperties props) {
         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
-        executor.setCorePoolSize(provisioningProperties.getAsyncConnectorFacadeExecutor().getCorePoolSize());
-        executor.setMaxPoolSize(provisioningProperties.getAsyncConnectorFacadeExecutor().getMaxPoolSize());
-        executor.setQueueCapacity(provisioningProperties.getAsyncConnectorFacadeExecutor().getQueueCapacity());
+        executor.setCorePoolSize(props.getAsyncConnectorFacadeExecutor().getCorePoolSize());
+        executor.setMaxPoolSize(props.getAsyncConnectorFacadeExecutor().getMaxPoolSize());
+        executor.setQueueCapacity(props.getAsyncConnectorFacadeExecutor().getQueueCapacity());
+        executor.setAwaitTerminationSeconds(props.getAsyncConnectorFacadeExecutor().getAwaitTerminationSeconds());
+        executor.setWaitForTasksToCompleteOnShutdown(true);
         executor.setThreadNamePrefix("AsyncConnectorFacadeExecutor-");
         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
         executor.initialize();
@@ -238,7 +238,8 @@ public class ProvisioningContext {
     }
 
     @Bean
-    public AsyncConfigurer asyncConfigurer(@Qualifier("asyncConnectorFacadeExecutor")
+    public AsyncConfigurer asyncConfigurer(
+            @Qualifier("asyncConnectorFacadeExecutor")
             final ThreadPoolTaskExecutor asyncConnectorFacadeExecutor) {
 
         return new AsyncConfigurer() {
@@ -253,16 +254,18 @@ public class ProvisioningContext {
     /**
      * Used by {@link org.apache.syncope.core.provisioning.java.propagation.PriorityPropagationTaskExecutor}.
      *
-     * @param provisioningProperties the provisioning properties
+     * @param props the provisioning properties
      * @return executor thread pool task executor
      */
     @Bean
-    public ThreadPoolTaskExecutor propagationTaskExecutorAsyncExecutor(
-            final ProvisioningProperties provisioningProperties) {
+    public ThreadPoolTaskExecutor propagationTaskExecutorAsyncExecutor(final ProvisioningProperties props) {
         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
-        executor.setCorePoolSize(provisioningProperties.getPropagationTaskExecutorAsyncExecutor().getCorePoolSize());
-        executor.setMaxPoolSize(provisioningProperties.getPropagationTaskExecutorAsyncExecutor().getMaxPoolSize());
-        executor.setQueueCapacity(provisioningProperties.getPropagationTaskExecutorAsyncExecutor().getQueueCapacity());
+        executor.setCorePoolSize(props.getPropagationTaskExecutorAsyncExecutor().getCorePoolSize());
+        executor.setMaxPoolSize(props.getPropagationTaskExecutorAsyncExecutor().getMaxPoolSize());
+        executor.setQueueCapacity(props.getPropagationTaskExecutorAsyncExecutor().getQueueCapacity());
+        executor.setWaitForTasksToCompleteOnShutdown(true);
+        executor.setAwaitTerminationSeconds(
+                props.getPropagationTaskExecutorAsyncExecutor().getAwaitTerminationSeconds());
         executor.setThreadNamePrefix("PropagationTaskExecutor-");
         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
         executor.initialize();
@@ -287,12 +290,11 @@ public class ProvisioningContext {
     @DependsOn("quartzDataSourceInit")
     @Lazy(false)
     @Bean
-    public SchedulerFactoryBean scheduler(final ApplicationContext ctx,
-            final ProvisioningProperties provisioningProperties) {
+    public SchedulerFactoryBean scheduler(final ApplicationContext ctx, final ProvisioningProperties props) {
         SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
         scheduler.setAutoStartup(true);
         scheduler.setApplicationContext(ctx);
-        scheduler.setWaitForJobsToCompleteOnShutdown(true);
+        scheduler.setWaitForJobsToCompleteOnShutdown(props.getQuartz().isWaitForJobsToCompleteOnShutdown());
         scheduler.setOverwriteExistingJobs(true);
         scheduler.setDataSource(masterDataSource);
         scheduler.setTransactionManager(masterTransactionManager);
@@ -301,16 +303,21 @@ public class ProvisioningContext {
         Properties quartzProperties = new Properties();
         quartzProperties.setProperty(
                 "org.quartz.scheduler.idleWaitTime",
-                String.valueOf(provisioningProperties.getQuartz().getIdleWaitTime()));
+                String.valueOf(props.getQuartz().getIdleWaitTime()));
         quartzProperties.setProperty(
                 "org.quartz.jobStore.misfireThreshold",
-                String.valueOf(provisioningProperties.getQuartz().getMisfireThreshold()));
+                String.valueOf(props.getQuartz().getMisfireThreshold()));
         quartzProperties.setProperty(
                 "org.quartz.jobStore.driverDelegateClass",
-                provisioningProperties.getQuartz().getDelegate().getName());
+                props.getQuartz().getDelegate().getName());
+        quartzProperties.setProperty(
+                "org.quartz.jobStore.class",
+                "org.springframework.scheduling.quartz.LocalDataSourceJobStore");
+        quartzProperties.setProperty("org.quartz.threadPool.makeThreadsDaemons", "true");
+        quartzProperties.setProperty("org.quartz.scheduler.makeSchedulerThreadDaemon", "true");
         quartzProperties.setProperty("org.quartz.jobStore.isClustered", "true");
         quartzProperties.setProperty("org.quartz.jobStore.clusterCheckinInterval", "20000");
-        quartzProperties.setProperty("org.quartz.scheduler.instanceName", "ClusteredScheduler");
+        quartzProperties.setProperty("org.quartz.scheduler.instanceName", "SyncopeClusteredScheduler");
         quartzProperties.setProperty("org.quartz.scheduler.instanceId", "AUTO");
         quartzProperties.setProperty("org.quartz.scheduler.jmx.export", "true");
         scheduler.setQuartzProperties(quartzProperties);
@@ -318,11 +325,6 @@ public class ProvisioningContext {
         return scheduler;
     }
 
-    @Bean
-    public SchedulerShutdown schedulerShutdown(final ApplicationContext ctx) {
-        return new SchedulerShutdown(ctx);
-    }
-
     @ConditionalOnMissingBean
     @Bean
     public JobManager jobManager(
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningProperties.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningProperties.java
index 2697866..7375f3d 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningProperties.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningProperties.java
@@ -28,39 +28,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
 @ConfigurationProperties("provisioning")
 public class ProvisioningProperties {
 
-    public static class ExecutorProperties {
-
-        private int corePoolSize = 5;
-
-        private int maxPoolSize = 25;
-
-        private int queueCapacity = 100;
-
-        public int getCorePoolSize() {
-            return corePoolSize;
-        }
-
-        public void setCorePoolSize(final int corePoolSize) {
-            this.corePoolSize = corePoolSize;
-        }
-
-        public int getMaxPoolSize() {
-            return maxPoolSize;
-        }
-
-        public void setMaxPoolSize(final int maxPoolSize) {
-            this.maxPoolSize = maxPoolSize;
-        }
-
-        public int getQueueCapacity() {
-            return queueCapacity;
-        }
-
-        public void setQueueCapacity(final int queueCapacity) {
-            this.queueCapacity = queueCapacity;
-        }
-    }
-
     public static class QuartzProperties {
 
         private Class<? extends DriverDelegate> delegate;
@@ -69,6 +36,8 @@ public class ProvisioningProperties {
 
         private boolean disableInstance = false;
 
+        private boolean waitForJobsToCompleteOnShutdown = true;
+
         private int idleWaitTime = 30000;
 
         private int misfireThreshold = 60000;
@@ -97,6 +66,14 @@ public class ProvisioningProperties {
             this.disableInstance = disableInstance;
         }
 
+        public boolean isWaitForJobsToCompleteOnShutdown() {
+            return waitForJobsToCompleteOnShutdown;
+        }
+
+        public void setWaitForJobsToCompleteOnShutdown(final boolean waitForJobsToCompleteOnShutdown) {
+            this.waitForJobsToCompleteOnShutdown = waitForJobsToCompleteOnShutdown;
+        }
+
         public int getIdleWaitTime() {
             return idleWaitTime;
         }
diff --git a/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreApplication.java b/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreApplication.java
index 7d35a1e..48f1e46 100644
--- a/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreApplication.java
+++ b/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreApplication.java
@@ -22,7 +22,6 @@ import java.util.Map;
 import org.apache.cxf.spring.boot.autoconfigure.openapi.OpenApiAutoConfiguration;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
 import org.apache.syncope.common.keymaster.client.api.ServiceOps;
-import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
 import org.apache.syncope.common.lib.info.SystemInfo;
 import org.apache.syncope.core.logic.LogicProperties;
@@ -66,6 +65,7 @@ import org.apache.syncope.core.starter.actuate.SyncopeCoreInfoContributor;
 import org.apache.syncope.core.workflow.api.AnyObjectWorkflowAdapter;
 import org.apache.syncope.core.workflow.api.GroupWorkflowAdapter;
 import org.apache.syncope.core.workflow.api.UserWorkflowAdapter;
+import org.springframework.beans.factory.ListableBeanFactory;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.actuate.mail.MailHealthIndicator;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -76,6 +76,7 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
 import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
 import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
+import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
 import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
 import org.springframework.boot.builder.SpringApplicationBuilder;
 import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@@ -87,14 +88,17 @@ import org.springframework.mail.javamail.JavaMailSenderImpl;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
 
-@SpringBootApplication(exclude = {
-    ErrorMvcAutoConfiguration.class,
-    HttpMessageConvertersAutoConfiguration.class,
-    OpenApiAutoConfiguration.class,
-    DataSourceAutoConfiguration.class,
-    DataSourceTransactionManagerAutoConfiguration.class,
-    JdbcTemplateAutoConfiguration.class,
-    QuartzAutoConfiguration.class }, proxyBeanMethods = false)
+@SpringBootApplication(
+        exclude = {
+            ErrorMvcAutoConfiguration.class,
+            HttpMessageConvertersAutoConfiguration.class,
+            OpenApiAutoConfiguration.class,
+            DataSourceAutoConfiguration.class,
+            DataSourceTransactionManagerAutoConfiguration.class,
+            JdbcTemplateAutoConfiguration.class,
+            QuartzAutoConfiguration.class,
+            TaskExecutionAutoConfiguration.class },
+        proxyBeanMethods = false)
 @EnableTransactionManagement
 public class SyncopeCoreApplication extends SpringBootServletInitializer {
 
@@ -111,6 +115,24 @@ public class SyncopeCoreApplication extends SpringBootServletInitializer {
 
     @ConditionalOnMissingBean
     @Bean
+    public TaskExecutorUnloader taskExecutorUnloader(final ListableBeanFactory beanFactory) {
+        return new TaskExecutorUnloader(beanFactory);
+    }
+
+    @ConditionalOnMissingBean
+    @Bean
+    public SyncopeCoreStart keymasterStart(final DomainHolder domainHolder) {
+        return new SyncopeCoreStart(domainHolder);
+    }
+
+    @ConditionalOnMissingBean
+    @Bean
+    public KeymasterStop keymasterStop(final DomainHolder domainHolder) {
+        return new SyncopeCoreStop(domainHolder);
+    }
+
+    @ConditionalOnMissingBean
+    @Bean
     public SyncopeCoreInfoContributor syncopeCoreInfoContributor(
             final SecurityProperties securityProperties,
             final PersistenceProperties persistenceProperties,
@@ -218,21 +240,11 @@ public class SyncopeCoreApplication extends SpringBootServletInitializer {
         return new EntityCacheEndpoint(entityCacheDAO);
     }
 
-    @ConditionalOnMissingBean
-    @Bean
-    public SyncopeCoreStart keymasterStart(final DomainHolder domainHolder) {
-        return new SyncopeCoreStart(domainHolder);
-    }
-
-    @Bean
-    public KeymasterStop keymasterStop() {
-        return new KeymasterStop(NetworkService.Type.CORE);
-    }
-
     @Bean
     public SyncopeStarterEventListener syncopeCoreEventListener(
             @Qualifier("syncopeCoreInfoContributor")
             final SyncopeCoreInfoContributor syncopeCoreInfoContributor) {
+
         return new DefaultSyncopeStarterEventListener(syncopeCoreInfoContributor);
     }
 
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 d913674..5a27238 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
@@ -30,7 +30,7 @@ import org.springframework.context.event.ContextRefreshedEvent;
 import org.springframework.core.Ordered;
 
 /**
- * Take care of all initializations needed by Syncope Core to run up and safe.
+ * Take care of all inits needed by Syncope Core to run up and safe.
  */
 public class SyncopeCoreStart extends KeymasterStart implements Ordered {
 
@@ -55,17 +55,17 @@ public class SyncopeCoreStart extends KeymasterStart implements Ordered {
                 forEachOrdered(loader -> {
                     String loaderName = AopUtils.getTargetClass(loader).getName();
 
-                    LOG.debug("[{}] Starting initialization", loaderName);
+                    LOG.debug("[{}#{}] Starting init", loaderName, loader.getOrder());
 
                     loader.load();
 
                     domainHolder.getDomains().forEach((domain, datasource) -> {
-                        LOG.debug("[{}] Starting on domain '{}'", loaderName, domain);
+                        LOG.debug("[{}] Starting init on domain '{}'", loaderName, domain);
                         loader.load(domain, datasource);
-                        LOG.debug("[{}] Completed on domain '{}'", loaderName, domain);
+                        LOG.debug("[{}] Init completed on domain '{}'", loaderName, domain);
                     });
 
-                    LOG.debug("[{}] Initialization completed", loaderName);
+                    LOG.debug("[{}] Init completed", loaderName);
                 });
 
         super.onApplicationEvent(event);
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/SyncopeCoreStop.java
similarity index 71%
copy from core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStart.java
copy to core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStop.java
index d913674..d55afc7 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/SyncopeCoreStop.java
@@ -20,25 +20,25 @@ package org.apache.syncope.core.starter;
 
 import java.util.Comparator;
 import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
-import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart;
+import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
 import org.apache.syncope.core.persistence.api.DomainHolder;
 import org.apache.syncope.core.persistence.api.SyncopeCoreLoader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.aop.support.AopUtils;
-import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.context.event.ContextClosedEvent;
 import org.springframework.core.Ordered;
 
 /**
- * Take care of all initializations needed by Syncope Core to run up and safe.
+ * Take care of all disposal needed by Syncope Core to shut down gracefully..
  */
-public class SyncopeCoreStart extends KeymasterStart implements Ordered {
+public class SyncopeCoreStop extends KeymasterStop implements Ordered {
 
-    private static final Logger LOG = LoggerFactory.getLogger(SyncopeCoreStart.class);
+    private static final Logger LOG = LoggerFactory.getLogger(SyncopeCoreStop.class);
 
     private final DomainHolder domainHolder;
 
-    public SyncopeCoreStart(final DomainHolder domainHolder) {
+    public SyncopeCoreStop(final DomainHolder domainHolder) {
         super(NetworkService.Type.CORE);
         this.domainHolder = domainHolder;
     }
@@ -49,23 +49,23 @@ public class SyncopeCoreStart extends KeymasterStart implements Ordered {
     }
 
     @Override
-    public void onApplicationEvent(final ContextRefreshedEvent event) {
+    public void onApplicationEvent(final ContextClosedEvent event) {
         event.getApplicationContext().getBeansOfType(SyncopeCoreLoader.class).values().stream().
-                sorted(Comparator.comparing(SyncopeCoreLoader::getOrder)).
+                sorted(Comparator.comparing(SyncopeCoreLoader::getOrder).reversed()).
                 forEachOrdered(loader -> {
                     String loaderName = AopUtils.getTargetClass(loader).getName();
 
-                    LOG.debug("[{}] Starting initialization", loaderName);
-
-                    loader.load();
+                    LOG.debug("[{}#{}] Starting dispose", loaderName, loader.getOrder());
 
                     domainHolder.getDomains().forEach((domain, datasource) -> {
-                        LOG.debug("[{}] Starting on domain '{}'", loaderName, domain);
-                        loader.load(domain, datasource);
-                        LOG.debug("[{}] Completed on domain '{}'", loaderName, domain);
+                        LOG.debug("[{}] Starting dispose on domain '{}'", loaderName, domain);
+                        loader.unload(domain);
+                        LOG.debug("[{}] Dispose completed on domain '{}'", loaderName, domain);
                     });
 
-                    LOG.debug("[{}] Initialization completed", loaderName);
+                    loader.unload();
+
+                    LOG.debug("[{}] Dispose completed", loaderName);
                 });
 
         super.onApplicationEvent(event);
diff --git a/core/starter/src/main/java/org/apache/syncope/core/starter/TaskExecutorUnloader.java b/core/starter/src/main/java/org/apache/syncope/core/starter/TaskExecutorUnloader.java
new file mode 100644
index 0000000..5363b63
--- /dev/null
+++ b/core/starter/src/main/java/org/apache/syncope/core/starter/TaskExecutorUnloader.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.starter;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.syncope.core.persistence.api.SyncopeCoreLoader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.scheduling.concurrent.ExecutorConfigurationSupport;
+
+public class TaskExecutorUnloader implements SyncopeCoreLoader {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(TaskExecutorUnloader.class);
+
+    protected final ListableBeanFactory beanFactory;
+
+    protected final Map<String, ExecutorConfigurationSupport> tptes = new HashMap<>();
+
+    public TaskExecutorUnloader(final ListableBeanFactory beanFactory) {
+        this.beanFactory = beanFactory;
+    }
+
+    @Override
+    public int getOrder() {
+        return Integer.MIN_VALUE;
+    }
+
+    @Override
+    public void load() {
+        tptes.putAll(beanFactory.getBeansOfType(ExecutorConfigurationSupport.class));
+    }
+
+    @Override
+    public void unload() {
+        tptes.forEach((name, tpte) -> {
+            LOG.info("Shutting down ThreadPoolTaskExecutor {}...", name);
+            try {
+                tpte.shutdown();
+
+                LOG.info("Successfully shut down ThreadPoolTaskExecutor {}", name);
+            } catch (Exception e) {
+                LOG.error("While shutting down ThreadPoolTaskExecutor {}", name, e);
+            }
+        });
+    }
+}
diff --git a/core/starter/src/main/resources/core.properties b/core/starter/src/main/resources/core.properties
index b2bb571..bee222e 100644
--- a/core/starter/src/main/resources/core.properties
+++ b/core/starter/src/main/resources/core.properties
@@ -34,6 +34,9 @@ management.endpoint.health.show-details=ALWAYS
 
 service.discovery.address=http://localhost:8080/syncope/rest/
 
+server.shutdown=graceful
+spring.lifecycle.timeout-per-shutdown-phase=30s
+
 ###############
 # Persistence #
 ###############
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngine.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngine.java
index 1f08981..5651f9d 100644
--- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngine.java
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngine.java
@@ -58,7 +58,7 @@ public class DomainProcessEngine implements ProcessEngine {
 
     @Override
     public void close() {
-        engines.get(AuthContextUtils.getDomain()).close();
+        engines.values().forEach(ProcessEngine::close);
     }
 
     @Override
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngineFactoryBean.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngineFactoryBean.java
index 7324d16..25ae72e 100644
--- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngineFactoryBean.java
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngineFactoryBean.java
@@ -22,6 +22,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import javax.annotation.PreDestroy;
 import javax.sql.DataSource;
 import org.apache.syncope.core.persistence.api.DomainHolder;
 import org.apache.syncope.core.persistence.api.SyncopeCoreLoader;
@@ -33,7 +34,6 @@ import org.flowable.idm.spring.SpringIdmEngineConfiguration;
 import org.flowable.spring.SpringProcessEngineConfiguration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.DisposableBean;
 import org.springframework.beans.factory.FactoryBean;
 import org.springframework.context.ApplicationContext;
 import org.springframework.transaction.PlatformTransactionManager;
@@ -42,8 +42,7 @@ import org.springframework.transaction.PlatformTransactionManager;
  * Spring factory for {@link DomainProcessEngine} which takes the provided {@link SpringProcessEngineConfiguration} as
  * template for each of the configured Syncope domains.
  */
-public class DomainProcessEngineFactoryBean
-        implements FactoryBean<DomainProcessEngine>, DisposableBean, SyncopeCoreLoader {
+public class DomainProcessEngineFactoryBean implements FactoryBean<DomainProcessEngine>, SyncopeCoreLoader {
 
     private static final Logger LOG = LoggerFactory.getLogger(DomainProcessEngineFactoryBean.class);
 
@@ -120,8 +119,8 @@ public class DomainProcessEngineFactoryBean
         return true;
     }
 
-    @Override
-    public void destroy() throws Exception {
+    @PreDestroy
+    public void preDestroy() {
         if (engine != null) {
             engine.close();
         }
diff --git a/fit/core-reference/src/main/resources/core-embedded.properties b/fit/core-reference/src/main/resources/core-embedded.properties
index 6ace161..3926b09 100644
--- a/fit/core-reference/src/main/resources/core-embedded.properties
+++ b/fit/core-reference/src/main/resources/core-embedded.properties
@@ -63,6 +63,7 @@ persistence.domain[1].adminCipherAlgorithm=SHA
 
 provisioning.quartz.delegate=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
 provisioning.quartz.sql=tables_h2.sql
+provisioning.quartz.waitForJobsToCompleteOnShutdown=false
 
 provisioning.connIdLocation=${connid.location},\
 connid://${testconnectorserver.key}@localhost:${testconnectorserver.port}
diff --git a/pom.xml b/pom.xml
index 3294e3c..a33fe6e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -418,8 +418,6 @@ under the License.
 
     <jasypt.version>1.9.3</jasypt.version>
 
-    <quartz.version>2.3.2</quartz.version>
-
     <cocoon.version>3.0.0-alpha-3</cocoon.version>
 
     <groovy.version>4.0.1</groovy.version>
@@ -920,6 +918,18 @@ under the License.
 
       <dependency>
         <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-quartz</artifactId>
+        <version>${spring-boot.version}</version>
+        <exclusions>
+          <exclusion>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-logging</artifactId>
+          </exclusion>
+        </exclusions>
+      </dependency>
+
+      <dependency>
+        <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-logging</artifactId>
         <exclusions>
           <exclusion>
@@ -1159,26 +1169,6 @@ under the License.
       </dependency>
 
       <dependency>
-        <groupId>org.quartz-scheduler</groupId>
-        <artifactId>quartz</artifactId>
-        <version>${quartz.version}</version>
-        <exclusions>
-          <exclusion>
-            <groupId>com.zaxxer</groupId>
-            <artifactId>HikariCP-java7</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>com.mchange</groupId>
-            <artifactId>c3p0</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>com.mchange</groupId>
-            <artifactId>mchange-commons-java</artifactId>
-          </exclusion>
-        </exclusions>
-      </dependency>
-
-      <dependency>
         <groupId>org.apache.cocoon.sax</groupId>
         <artifactId>cocoon-sax</artifactId>
         <version>${cocoon.version}</version>
diff --git a/sra/src/main/resources/sra.properties b/sra/src/main/resources/sra.properties
index 551efc7..1e42d3e 100644
--- a/sra/src/main/resources/sra.properties
+++ b/sra/src/main/resources/sra.properties
@@ -27,6 +27,9 @@ spring.cloud.discovery.client.health-indicator.enabled=false
 
 service.discovery.address=http://localhost:8080/
 
+server.shutdown=graceful
+spring.lifecycle.timeout-per-shutdown-phase=30s
+
 logging.config=classpath:log4j2.xml
 
 sra.anonymousUser=${anonymousUser}
diff --git a/wa/starter/src/main/resources/wa.properties b/wa/starter/src/main/resources/wa.properties
index e2418d7..bd29731 100644
--- a/wa/starter/src/main/resources/wa.properties
+++ b/wa/starter/src/main/resources/wa.properties
@@ -58,6 +58,9 @@ spring.main.lazy-initialization=false
 
 service.discovery.address=http://localhost:8080/syncope-wa/
 
+server.shutdown=graceful
+spring.lifecycle.timeout-per-shutdown-phase=30s
+
 wa.anonymousUser=${anonymousUser}
 wa.anonymousKey=${anonymousKey}