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/05/07 15:22:05 UTC

[syncope] branch 2_1_X updated: [SYNCOPE-1674] Optimizing User, Group and Any Object lifecycle event publishing

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

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


The following commit(s) were added to refs/heads/2_1_X by this push:
     new 56ebaf2aac [SYNCOPE-1674] Optimizing User, Group and Any Object lifecycle event publishing
56ebaf2aac is described below

commit 56ebaf2aac6c9f5c38643ad8ad7049494e7ff1d3
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Sat May 7 17:21:14 2022 +0200

    [SYNCOPE-1674] Optimizing User, Group and Any Object lifecycle event publishing
---
 .../persistence/jpa/dao/JPAJSONAnyObjectDAO.java   |  4 --
 .../core/persistence/jpa/dao/JPAJSONUserDAO.java   |  4 --
 .../core/persistence/jpa/dao/AbstractAnyDAO.java   |  4 --
 .../core/persistence/jpa/dao/JPAAnyObjectDAO.java  |  5 --
 .../core/persistence/jpa/dao/JPADynRealmDAO.java   |  9 ++-
 .../core/persistence/jpa/dao/JPAGroupDAO.java      | 41 +++++++-----
 .../core/persistence/jpa/dao/JPARoleDAO.java       | 11 +--
 .../core/persistence/jpa/dao/JPAUserDAO.java       |  6 --
 .../provisioning/api/event/AnyDeletedEvent.java    | 65 ------------------
 ...tedUpdatedEvent.java => AnyLifecycleEvent.java} | 14 +++-
 .../java/DefaultAnyObjectWorkflowAdapter.java      | 18 +++++
 .../workflow/java/DefaultGroupWorkflowAdapter.java | 18 +++++
 .../workflow/java/DefaultUserWorkflowAdapter.java  | 33 ++++++++-
 .../client/ElasticsearchIndexManager.java          | 78 +++++++++++-----------
 .../core/flowable/api/UserRequestHandler.java      |  5 +-
 .../flowable/impl/FlowableUserRequestHandler.java  | 22 +++---
 .../flowable/impl/FlowableUserWorkflowAdapter.java | 45 ++++++++++++-
 17 files changed, 211 insertions(+), 171 deletions(-)

diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAJSONAnyObjectDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAJSONAnyObjectDAO.java
index 253f382b9a..d38fe48502 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAJSONAnyObjectDAO.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAJSONAnyObjectDAO.java
@@ -30,8 +30,6 @@ import org.apache.syncope.core.persistence.api.dao.JPAJSONAnyDAO;
 import org.apache.syncope.core.persistence.api.entity.DerSchema;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrUniqueValue;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
-import org.apache.syncope.core.provisioning.api.event.AnyCreatedUpdatedEvent;
-import org.apache.syncope.core.spring.security.AuthContextUtils;
 
 public class JPAJSONAnyObjectDAO extends JPAAnyObjectDAO {
 
@@ -80,8 +78,6 @@ public class JPAJSONAnyObjectDAO extends JPAAnyObjectDAO {
         // ensure that entity listeners are invoked at this point
         entityManager().flush();
 
-        publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, merged, AuthContextUtils.getDomain()));
-
         Pair<Set<String>, Set<String>> dynGroupMembs = groupDAO.refreshDynMemberships(merged);
         dynRealmDAO.refreshDynMemberships(merged);
 
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAJSONUserDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAJSONUserDAO.java
index 13de10eb03..80daf70f65 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAJSONUserDAO.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAJSONUserDAO.java
@@ -32,8 +32,6 @@ import org.apache.syncope.core.persistence.api.entity.DerSchema;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrUniqueValue;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUser;
-import org.apache.syncope.core.provisioning.api.event.AnyCreatedUpdatedEvent;
-import org.apache.syncope.core.spring.security.AuthContextUtils;
 
 public class JPAJSONUserDAO extends JPAUserDAO {
 
@@ -98,8 +96,6 @@ public class JPAJSONUserDAO extends JPAUserDAO {
         // ensure that entity listeners are invoked at this point
         entityManager().flush();
 
-        publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, merged, AuthContextUtils.getDomain()));
-
         roleDAO.refreshDynMemberships(merged);
         Pair<Set<String>, Set<String>> dynGroupMembs = groupDAO.refreshDynMemberships(merged);
         dynRealmDAO.refreshDynMemberships(merged);
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java
index 43caaa8af4..822fc497d7 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java
@@ -64,7 +64,6 @@ import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUser;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
@@ -74,9 +73,6 @@ public abstract class AbstractAnyDAO<A extends Any<?>> extends AbstractDAO<A> im
     @Autowired
     protected AnyUtilsFactory anyUtilsFactory;
 
-    @Autowired
-    protected ApplicationEventPublisher publisher;
-
     @Autowired
     @Lazy
     protected PlainSchemaDAO plainSchemaDAO;
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
index aa76215e8f..69b71f3abe 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
@@ -56,8 +56,6 @@ import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAMembership;
 import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAARelationship;
 import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAnyObject;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAURelationship;
-import org.apache.syncope.core.provisioning.api.event.AnyCreatedUpdatedEvent;
-import org.apache.syncope.core.provisioning.api.event.AnyDeletedEvent;
 import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Propagation;
@@ -221,7 +219,6 @@ public class JPAAnyObjectDAO extends AbstractAnyDAO<AnyObject> implements AnyObj
 
     protected Pair<AnyObject, Pair<Set<String>, Set<String>>> doSave(final AnyObject anyObject) {
         AnyObject merged = super.save(anyObject);
-        publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, merged, AuthContextUtils.getDomain()));
 
         Pair<Set<String>, Set<String>> dynGroupMembs = groupDAO.refreshDynMemberships(merged);
         dynRealmDAO.refreshDynMemberships(merged);
@@ -276,8 +273,6 @@ public class JPAAnyObjectDAO extends AbstractAnyDAO<AnyObject> implements AnyObj
         });
 
         entityManager().remove(anyObject);
-        publisher.publishEvent(new AnyDeletedEvent(
-                this, AnyTypeKind.ANY_OBJECT, anyObject.getKey(), anyObject.getName(), AuthContextUtils.getDomain()));
     }
 
     @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADynRealmDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADynRealmDAO.java
index 837e04360c..a3f23d9f7a 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADynRealmDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADynRealmDAO.java
@@ -28,7 +28,6 @@ import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.DynRealm;
 import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
 import org.apache.syncope.core.persistence.jpa.entity.JPADynRealm;
-import org.apache.syncope.core.provisioning.api.event.AnyCreatedUpdatedEvent;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationEventPublisher;
@@ -39,6 +38,8 @@ import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
+import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.identityconnectors.framework.common.objects.SyncDeltaType;
 
 @Repository
 public class JPADynRealmDAO extends AbstractDAO<DynRealm> implements DynRealmDAO {
@@ -112,7 +113,8 @@ public class JPADynRealmDAO extends AbstractDAO<DynRealm> implements DynRealmDAO
                 any = anyObjectDAO.find(key);
             }
             if (any != null) {
-                publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, any, AuthContextUtils.getDomain()));
+                publisher.publishEvent(
+                        new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, any, AuthContextUtils.getDomain()));
             }
         });
     }
@@ -133,7 +135,8 @@ public class JPADynRealmDAO extends AbstractDAO<DynRealm> implements DynRealmDAO
             insert.setParameter(2, merged.getKey());
             insert.executeUpdate();
 
-            publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, any, AuthContextUtils.getDomain()));
+            publisher.publishEvent(
+                    new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, any, AuthContextUtils.getDomain()));
             cleared.remove(any.getKey());
         }));
 
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java
index a79896ab2e..bccb1800f5 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java
@@ -64,12 +64,13 @@ import org.apache.syncope.core.persistence.jpa.entity.group.JPAGroup;
 import org.apache.syncope.core.persistence.jpa.entity.group.JPATypeExtension;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUDynGroupMembership;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUMembership;
-import org.apache.syncope.core.provisioning.api.event.AnyCreatedUpdatedEvent;
-import org.apache.syncope.core.provisioning.api.event.AnyDeletedEvent;
+import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
 import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
+import org.identityconnectors.framework.common.objects.SyncDeltaType;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.transaction.annotation.Transactional;
 
 public class JPAGroupDAO extends AbstractAnyDAO<Group> implements GroupDAO {
@@ -96,6 +97,9 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group> implements GroupDAO {
     @Autowired
     private SearchCondVisitor searchCondVisitor;
 
+    @Autowired
+    protected ApplicationEventPublisher publisher;
+
     @Override
     protected AnyUtils init() {
         return anyUtilsFactory.getInstance(AnyTypeKind.GROUP);
@@ -272,7 +276,6 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group> implements GroupDAO {
     @Override
     public Group saveAndRefreshDynMemberships(final Group group) {
         Group merged = save(group);
-        publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, merged, AuthContextUtils.getDomain()));
 
         // refresh dynamic memberships
         clearUDynMembers(merged);
@@ -295,7 +298,8 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group> implements GroupDAO {
                     insert.setParameter(2, merged.getKey());
                     insert.executeUpdate();
 
-                    publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, user, AuthContextUtils.getDomain()));
+                    publisher.publishEvent(
+                            new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, user, AuthContextUtils.getDomain()));
                 });
             }
         }
@@ -313,15 +317,16 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group> implements GroupDAO {
                         Collections.<OrderByClause>emptyList(),
                         AnyTypeKind.ANY_OBJECT);
 
-                matching.forEach(anyObject -> {
+                matching.forEach(any -> {
                     Query insert = entityManager().createNativeQuery(
                             "INSERT INTO " + ADYNMEMB_TABLE + " VALUES(?, ?, ?)");
-                    insert.setParameter(1, anyObject.getType().getKey());
-                    insert.setParameter(2, anyObject.getKey());
+                    insert.setParameter(1, any.getType().getKey());
+                    insert.setParameter(2, any.getKey());
                     insert.setParameter(3, merged.getKey());
                     insert.executeUpdate();
 
-                    publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, anyObject, AuthContextUtils.getDomain()));
+                    publisher.publishEvent(
+                            new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, any, AuthContextUtils.getDomain()));
                 });
             }
         });
@@ -347,7 +352,8 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group> implements GroupDAO {
             });
 
             anyObjectDAO.save(leftEnd);
-            publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, leftEnd, AuthContextUtils.getDomain()));
+            publisher.publishEvent(
+                    new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, leftEnd, AuthContextUtils.getDomain()));
         });
 
         findUMemberships(group).forEach(membership -> {
@@ -363,15 +369,14 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group> implements GroupDAO {
             });
 
             userDAO.save(leftEnd);
-            publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, leftEnd, AuthContextUtils.getDomain()));
+            publisher.publishEvent(
+                    new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, leftEnd, AuthContextUtils.getDomain()));
         });
 
         clearUDynMembers(group);
         clearADynMembers(group);
 
         entityManager().remove(group);
-        publisher.publishEvent(new AnyDeletedEvent(
-                this, AnyTypeKind.GROUP, group.getKey(), group.getName(), AuthContextUtils.getDomain()));
     }
 
     @Override
@@ -503,7 +508,8 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group> implements GroupDAO {
                 delete.executeUpdate();
             }
 
-            publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, memb.getGroup(), AuthContextUtils.getDomain()));
+            publisher.publishEvent(
+                    new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, memb.getGroup(), AuthContextUtils.getDomain()));
         });
 
         return Pair.of(before, after);
@@ -521,7 +527,8 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group> implements GroupDAO {
         dynGroups.forEach(group -> {
             before.add(group.getKey());
 
-            publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, group, AuthContextUtils.getDomain()));
+            publisher.publishEvent(
+                    new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, group, AuthContextUtils.getDomain()));
         });
 
         return before;
@@ -601,7 +608,8 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group> implements GroupDAO {
                 delete.executeUpdate();
             }
 
-            publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, memb.getGroup(), AuthContextUtils.getDomain()));
+            publisher.publishEvent(
+                    new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, memb.getGroup(), AuthContextUtils.getDomain()));
         });
 
         return Pair.of(before, after);
@@ -619,7 +627,8 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group> implements GroupDAO {
         dynGroups.forEach(group -> {
             before.add(group.getKey());
 
-            publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, group, AuthContextUtils.getDomain()));
+            publisher.publishEvent(
+                    new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, group, AuthContextUtils.getDomain()));
         });
 
         return before;
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java
index bae0b98bd9..d3e2e8f6f3 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java
@@ -36,8 +36,9 @@ import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
 import org.apache.syncope.core.persistence.jpa.entity.JPARole;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUser;
-import org.apache.syncope.core.provisioning.api.event.AnyCreatedUpdatedEvent;
+import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.identityconnectors.framework.common.objects.SyncDeltaType;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.stereotype.Repository;
@@ -115,13 +116,14 @@ public class JPARoleDAO extends AbstractDAO<Role> implements RoleDAO {
                     SearchCondConverter.convert(searchCondVisitor, merged.getDynMembership().getFIQLCond()),
                     AnyTypeKind.USER);
 
-            matching.forEach((user) -> {
+            matching.forEach(user -> {
                 Query insert = entityManager().createNativeQuery("INSERT INTO " + DYNMEMB_TABLE + " VALUES(?, ?)");
                 insert.setParameter(1, user.getKey());
                 insert.setParameter(2, merged.getKey());
                 insert.executeUpdate();
 
-                publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, user, AuthContextUtils.getDomain()));
+                publisher.publishEvent(
+                        new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, user, AuthContextUtils.getDomain()));
             });
         }
 
@@ -136,7 +138,8 @@ public class JPARoleDAO extends AbstractDAO<Role> implements RoleDAO {
 
         query.getResultList().forEach(user -> {
             user.getRoles().remove(role);
-            publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, user, AuthContextUtils.getDomain()));
+            publisher.publishEvent(
+                    new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, user, AuthContextUtils.getDomain()));
         });
 
         clearDynMembers(role);
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
index dd0592af6d..ce2168bcbb 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
@@ -68,8 +68,6 @@ import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPALinkedAccount;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUMembership;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUser;
-import org.apache.syncope.core.provisioning.api.event.AnyCreatedUpdatedEvent;
-import org.apache.syncope.core.provisioning.api.event.AnyDeletedEvent;
 import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
 import org.apache.syncope.core.spring.ImplementationManager;
 import org.apache.syncope.core.spring.security.Encryptor;
@@ -451,8 +449,6 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
             throw e;
         }
 
-        publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, merged, AuthContextUtils.getDomain()));
-
         roleDAO.refreshDynMemberships(merged);
         Pair<Set<String>, Set<String>> dynGroupMembs = groupDAO.refreshDynMemberships(merged);
         dynRealmDAO.refreshDynMemberships(merged);
@@ -489,8 +485,6 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
         }
 
         entityManager().remove(user);
-        publisher.publishEvent(new AnyDeletedEvent(
-                this, AnyTypeKind.USER, user.getKey(), user.getUsername(), AuthContextUtils.getDomain()));
     }
 
     @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/event/AnyDeletedEvent.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/event/AnyDeletedEvent.java
deleted file mode 100644
index d9f503598a..0000000000
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/event/AnyDeletedEvent.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.api.event;
-
-import org.apache.syncope.common.lib.types.AnyTypeKind;
-import org.springframework.context.ApplicationEvent;
-
-public class AnyDeletedEvent extends ApplicationEvent {
-
-    private static final long serialVersionUID = 6389886937942135639L;
-
-    private final AnyTypeKind anyTypeKind;
-
-    private final String anyKey;
-
-    private final String anyName;
-
-    private final String domain;
-
-    public AnyDeletedEvent(
-            final Object source,
-            final AnyTypeKind anyTypeKind,
-            final String anyKey,
-            final String anyName,
-            final String domain) {
-
-        super(source);
-        this.anyTypeKind = anyTypeKind;
-        this.anyKey = anyKey;
-        this.anyName = anyName;
-        this.domain = domain;
-    }
-
-    public AnyTypeKind getAnyTypeKind() {
-        return anyTypeKind;
-    }
-
-    public String getAnyKey() {
-        return anyKey;
-    }
-
-    public String getAnyName() {
-        return anyName;
-    }
-
-    public String getDomain() {
-        return domain;
-    }
-}
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/event/AnyCreatedUpdatedEvent.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/event/AnyLifecycleEvent.java
similarity index 77%
rename from core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/event/AnyCreatedUpdatedEvent.java
rename to core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/event/AnyLifecycleEvent.java
index 4a2f9a177c..8fbc75f1c3 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/event/AnyCreatedUpdatedEvent.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/event/AnyLifecycleEvent.java
@@ -19,22 +19,31 @@
 package org.apache.syncope.core.provisioning.api.event;
 
 import org.apache.syncope.core.persistence.api.entity.Any;
+import org.identityconnectors.framework.common.objects.SyncDeltaType;
 import org.springframework.context.ApplicationEvent;
 
-public class AnyCreatedUpdatedEvent<A extends Any<?>> extends ApplicationEvent {
+public class AnyLifecycleEvent<A extends Any<?>> extends ApplicationEvent {
 
     private static final long serialVersionUID = -781747175059834365L;
 
+    private final SyncDeltaType type;
+
     private final A any;
 
     private final String domain;
 
-    public AnyCreatedUpdatedEvent(final Object source, final A any, final String domain) {
+    public AnyLifecycleEvent(final Object source, final SyncDeltaType type, final A any, final String domain) {
         super(source);
+
+        this.type = type;
         this.any = any;
         this.domain = domain;
     }
 
+    public SyncDeltaType getType() {
+        return type;
+    }
+
     public A getAny() {
         return any;
     }
@@ -42,5 +51,4 @@ public class AnyCreatedUpdatedEvent<A extends Any<?>> extends ApplicationEvent {
     public String getDomain() {
         return domain;
     }
-
 }
diff --git a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultAnyObjectWorkflowAdapter.java b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultAnyObjectWorkflowAdapter.java
index 799c4b3041..2e178b7c15 100644
--- a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultAnyObjectWorkflowAdapter.java
+++ b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultAnyObjectWorkflowAdapter.java
@@ -24,18 +24,29 @@ import org.apache.syncope.core.provisioning.api.PropagationByResource;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.provisioning.api.WorkflowResult;
+import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.identityconnectors.framework.common.objects.SyncDeltaType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
 
 /**
  * Simple implementation basically not involving any workflow engine.
  */
 public class DefaultAnyObjectWorkflowAdapter extends AbstractAnyObjectWorkflowAdapter {
 
+    @Autowired
+    protected ApplicationEventPublisher publisher;
+
     @Override
     protected WorkflowResult<String> doCreate(final AnyObjectTO anyObjectTO) {
         AnyObject anyObject = entityFactory.newEntity(AnyObject.class);
         dataBinder.create(anyObject, anyObjectTO);
         anyObject = anyObjectDAO.save(anyObject);
 
+        publisher.publishEvent(
+                new AnyLifecycleEvent<>(this, SyncDeltaType.CREATE, anyObject, AuthContextUtils.getDomain()));
+
         PropagationByResource<String> propByRes = new PropagationByResource<>();
         propByRes.set(ResourceOperation.CREATE, anyObjectDAO.findAllResourceKeys(anyObject.getKey()));
 
@@ -45,11 +56,18 @@ public class DefaultAnyObjectWorkflowAdapter extends AbstractAnyObjectWorkflowAd
     @Override
     protected WorkflowResult<AnyObjectPatch> doUpdate(final AnyObject anyObject, final AnyObjectPatch anyObjectPatch) {
         PropagationByResource<String> propByRes = dataBinder.update(anyObject, anyObjectPatch);
+
+        publisher.publishEvent(new AnyLifecycleEvent<>(
+                this, SyncDeltaType.UPDATE, anyObjectDAO.find(anyObject.getKey()), AuthContextUtils.getDomain()));
+
         return new WorkflowResult<>(anyObjectPatch, propByRes, "update");
     }
 
     @Override
     protected void doDelete(final AnyObject anyObject) {
         anyObjectDAO.delete(anyObject);
+
+        publisher.publishEvent(
+                new AnyLifecycleEvent<>(this, SyncDeltaType.DELETE, anyObject, AuthContextUtils.getDomain()));
     }
 }
diff --git a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultGroupWorkflowAdapter.java b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultGroupWorkflowAdapter.java
index ffd6d33051..48ca3171ae 100644
--- a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultGroupWorkflowAdapter.java
+++ b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultGroupWorkflowAdapter.java
@@ -24,18 +24,29 @@ import org.apache.syncope.core.provisioning.api.PropagationByResource;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.provisioning.api.WorkflowResult;
+import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.identityconnectors.framework.common.objects.SyncDeltaType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
 
 /**
  * Simple implementation basically not involving any workflow engine.
  */
 public class DefaultGroupWorkflowAdapter extends AbstractGroupWorkflowAdapter {
 
+    @Autowired
+    protected ApplicationEventPublisher publisher;
+
     @Override
     protected WorkflowResult<String> doCreate(final GroupTO groupTO) {
         Group group = entityFactory.newEntity(Group.class);
         dataBinder.create(group, groupTO);
         group = groupDAO.saveAndRefreshDynMemberships(group);
 
+        publisher.publishEvent(
+                new AnyLifecycleEvent<>(this, SyncDeltaType.CREATE, group, AuthContextUtils.getDomain()));
+
         PropagationByResource<String> propByRes = new PropagationByResource<>();
         propByRes.set(ResourceOperation.CREATE, groupDAO.findAllResourceKeys(group.getKey()));
 
@@ -45,11 +56,18 @@ public class DefaultGroupWorkflowAdapter extends AbstractGroupWorkflowAdapter {
     @Override
     protected WorkflowResult<GroupPatch> doUpdate(final Group group, final GroupPatch groupPatch) {
         PropagationByResource<String> propByRes = dataBinder.update(group, groupPatch);
+     
+        publisher.publishEvent(new AnyLifecycleEvent<>(
+                this, SyncDeltaType.UPDATE, groupDAO.find(group.getKey()), AuthContextUtils.getDomain()));
+        
         return new WorkflowResult<>(groupPatch, propByRes, "update");
     }
 
     @Override
     protected void doDelete(final Group group) {
         groupDAO.delete(group);
+
+        publisher.publishEvent(
+                new AnyLifecycleEvent<>(this, SyncDeltaType.DELETE, group, AuthContextUtils.getDomain()));
     }
 }
diff --git a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java
index 06769c2ab5..8c6353c29c 100644
--- a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java
+++ b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java
@@ -27,8 +27,12 @@ import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.core.persistence.api.dao.ConfDAO;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
+import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.workflow.api.WorkflowException;
+import org.identityconnectors.framework.common.objects.SyncDeltaType;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
 
 /**
  * Simple implementation basically not involving any workflow engine.
@@ -36,7 +40,10 @@ import org.springframework.beans.factory.annotation.Autowired;
 public class DefaultUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
 
     @Autowired
-    private ConfDAO confDAO;
+    protected ApplicationEventPublisher publisher;
+
+    @Autowired
+    protected ConfDAO confDAO;
 
     @Override
     protected UserWorkflowResult<Pair<String, Boolean>> doCreate(
@@ -69,6 +76,9 @@ public class DefaultUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
         user.setStatus(status);
         user = userDAO.save(user);
 
+        publisher.publishEvent(
+                new AnyLifecycleEvent<>(this, SyncDeltaType.CREATE, user, AuthContextUtils.getDomain()));
+
         PropagationByResource<String> propByRes = new PropagationByResource<>();
         propByRes.set(ResourceOperation.CREATE, userDAO.findAllResourceKeys(user.getKey()));
 
@@ -94,6 +104,9 @@ public class DefaultUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
         user.setStatus("active");
         User updated = userDAO.save(user);
 
+        publisher.publishEvent(
+                new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated, AuthContextUtils.getDomain()));
+
         return new UserWorkflowResult<>(updated.getKey(), null, null, "activate");
     }
 
@@ -101,6 +114,10 @@ public class DefaultUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
     protected UserWorkflowResult<Pair<UserPatch, Boolean>> doUpdate(final User user, final UserPatch userPatch) {
         Pair<PropagationByResource<String>, PropagationByResource<Pair<String, String>>> propInfo =
                 dataBinder.update(user, userPatch);
+
+        publisher.publishEvent(new AnyLifecycleEvent<>(
+                this, SyncDeltaType.UPDATE, userDAO.find(user.getKey()), AuthContextUtils.getDomain()));
+
         return new UserWorkflowResult<>(
                 Pair.of(userPatch, !user.isSuspended()),
                 propInfo.getLeft(),
@@ -113,6 +130,9 @@ public class DefaultUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
         user.setStatus("suspended");
         User updated = userDAO.save(user);
 
+        publisher.publishEvent(
+                new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated, AuthContextUtils.getDomain()));
+
         return new UserWorkflowResult<>(updated.getKey(), null, null, "suspend");
     }
 
@@ -121,6 +141,9 @@ public class DefaultUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
         user.setStatus("active");
         User updated = userDAO.save(user);
 
+        publisher.publishEvent(
+                new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated, AuthContextUtils.getDomain()));
+
         return new UserWorkflowResult<>(updated.getKey(), null, null, "reactivate");
     }
 
@@ -129,7 +152,10 @@ public class DefaultUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
         user.generateToken(
                 confDAO.find("token.length", 256L).intValue(),
                 confDAO.find("token.expireTime", 60L).intValue());
-        userDAO.save(user);
+        User updated = userDAO.save(user);
+
+        publisher.publishEvent(
+                new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated, AuthContextUtils.getDomain()));
     }
 
     @Override
@@ -155,5 +181,8 @@ public class DefaultUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
     @Override
     protected void doDelete(final User user) {
         userDAO.delete(user);
+
+        publisher.publishEvent(
+                new AnyLifecycleEvent<>(this, SyncDeltaType.DELETE, user, AuthContextUtils.getDomain()));
     }
 }
diff --git a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexManager.java b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexManager.java
index 284831ff1e..7dc8db64d8 100644
--- a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexManager.java
+++ b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexManager.java
@@ -21,9 +21,7 @@ package org.apache.syncope.ext.elasticsearch.client;
 import java.io.IOException;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.core.persistence.api.entity.Any;
-import org.apache.syncope.core.provisioning.api.event.AnyCreatedUpdatedEvent;
-import org.apache.syncope.core.provisioning.api.event.AnyDeletedEvent;
-import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
 import org.elasticsearch.ElasticsearchStatusException;
 import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
 import org.elasticsearch.action.delete.DeleteRequest;
@@ -41,6 +39,7 @@ import org.elasticsearch.client.indices.CreateIndexResponse;
 import org.elasticsearch.client.indices.GetIndexRequest;
 import org.elasticsearch.xcontent.XContentBuilder;
 import org.elasticsearch.xcontent.XContentFactory;
+import org.identityconnectors.framework.common.objects.SyncDeltaType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.transaction.event.TransactionalEventListener;
@@ -150,45 +149,44 @@ public class ElasticsearchIndexManager {
     }
 
     @TransactionalEventListener
-    public void after(final AnyCreatedUpdatedEvent<Any<?>> event) throws IOException {
-        GetRequest getRequest = new GetRequest(
-                ElasticsearchUtils.getContextDomainName(
-                        AuthContextUtils.getDomain(), event.getAny().getType().getKind()),
-                event.getAny().getKey());
-        GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
-        if (getResponse.isExists()) {
-            LOG.debug("About to update index for {}", event.getAny());
-
-            UpdateRequest request = new UpdateRequest(
-                    ElasticsearchUtils.getContextDomainName(
-                            AuthContextUtils.getDomain(), event.getAny().getType().getKind()),
-                    event.getAny().getKey()).
-                    retryOnConflict(elasticsearchUtils.getRetryOnConflict()).
-                    doc(elasticsearchUtils.builder(event.getAny(), event.getDomain()));
-            UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
-            LOG.debug("Index successfully updated for {}: {}", event.getAny(), response);
+    public void after(final AnyLifecycleEvent<Any<?>> event) throws IOException {
+        LOG.debug("About to {} index for {}", event.getType().name(), event.getAny());
+
+        if (event.getType() == SyncDeltaType.DELETE) {
+            DeleteRequest request = new DeleteRequest(
+                    ElasticsearchUtils.getContextDomainName(event.getDomain(), event.getAny().getType().getKind()),
+                    event.getAny().getKey());
+            DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
+            LOG.debug("Index successfully deleted for {}[{}]: {}",
+                    event.getAny().getType().getKind(), event.getAny().getKey(), response);
         } else {
-            LOG.debug("About to create index for {}", event.getAny());
-
-            IndexRequest request = new IndexRequest(
+            GetRequest getRequest = new GetRequest(
                     ElasticsearchUtils.getContextDomainName(
-                            AuthContextUtils.getDomain(), event.getAny().getType().getKind())).
-                    id(event.getAny().getKey()).
-                    source(elasticsearchUtils.builder(event.getAny(), event.getDomain()));
-            IndexResponse response = client.index(request, RequestOptions.DEFAULT);
-            LOG.debug("Index successfully created for {}: {}", event.getAny(), response);
+                            event.getDomain(), event.getAny().getType().getKind()),
+                    event.getAny().getKey());
+            GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
+            if (getResponse.isExists()) {
+                LOG.debug("About to update index for {}", event.getAny());
+
+                UpdateRequest request = new UpdateRequest(
+                        ElasticsearchUtils.getContextDomainName(
+                                event.getDomain(), event.getAny().getType().getKind()),
+                        event.getAny().getKey()).
+                        retryOnConflict(elasticsearchUtils.getRetryOnConflict()).
+                        doc(elasticsearchUtils.builder(event.getAny(), event.getDomain()));
+                UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
+                LOG.debug("Index successfully updated for {}: {}", event.getAny(), response);
+            } else {
+                LOG.debug("About to create index for {}", event.getAny());
+
+                IndexRequest request = new IndexRequest(
+                        ElasticsearchUtils.getContextDomainName(
+                                event.getDomain(), event.getAny().getType().getKind())).
+                        id(event.getAny().getKey()).
+                        source(elasticsearchUtils.builder(event.getAny(), event.getDomain()));
+                IndexResponse response = client.index(request, RequestOptions.DEFAULT);
+                LOG.debug("Index successfully created for {}: {}", event.getAny(), response);
+            }
         }
     }
-
-    @TransactionalEventListener
-    public void after(final AnyDeletedEvent event) throws IOException {
-        LOG.debug("About to delete index for {}[{}]", event.getAnyTypeKind(), event.getAnyKey());
-
-        DeleteRequest request = new DeleteRequest(
-                ElasticsearchUtils.getContextDomainName(AuthContextUtils.getDomain(), event.getAnyTypeKind()),
-                event.getAnyKey());
-        DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
-        LOG.debug("Index successfully deleted for {}[{}]: {}",
-                event.getAnyTypeKind(), event.getAnyKey(), response);
-    }
 }
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
index 2321b87acb..189ce077b5 100644
--- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
@@ -25,9 +25,10 @@ import org.apache.syncope.common.lib.to.UserRequest;
 import org.apache.syncope.common.lib.to.UserRequestForm;
 import org.apache.syncope.common.lib.to.WorkflowTaskExecInput;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
-import org.apache.syncope.core.provisioning.api.event.AnyDeletedEvent;
+import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
 import org.flowable.engine.runtime.ProcessInstance;
 import org.springframework.transaction.event.TransactionalEventListener;
 
@@ -84,7 +85,7 @@ public interface UserRequestHandler {
      * @param event delete event
      */
     @TransactionalEventListener
-    void cancelByUser(AnyDeletedEvent event);
+    void cancelByUser(AnyLifecycleEvent<Any<?>> event);
 
     /**
      * Get the form matching the provided task id.
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
index 6fb4860811..cc25ac96c5 100644
--- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
@@ -36,7 +36,6 @@ import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.to.UserRequestFormProperty;
 import org.apache.syncope.common.lib.to.UserRequestForm;
 import org.apache.syncope.common.lib.to.WorkflowTaskExecInput;
-import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.UserRequestFormPropertyType;
 import org.apache.syncope.core.flowable.api.DropdownValueProvider;
@@ -45,12 +44,13 @@ import org.apache.syncope.core.flowable.support.DomainProcessEngine;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.PropagationByResource;
 import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
 import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
-import org.apache.syncope.core.provisioning.api.event.AnyDeletedEvent;
+import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.workflow.api.WorkflowException;
@@ -68,6 +68,7 @@ import org.flowable.task.api.Task;
 import org.flowable.task.api.TaskQuery;
 import org.flowable.task.api.history.HistoricTaskInstance;
 import org.flowable.variable.api.history.HistoricVariableInstance;
+import org.identityconnectors.framework.common.objects.SyncDeltaType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
@@ -261,15 +262,16 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
     }
 
     @Override
-    public void cancelByUser(final AnyDeletedEvent event) {
-        if (AuthContextUtils.getDomain().equals(event.getDomain()) && event.getAnyTypeKind() == AnyTypeKind.USER) {
-            String username = event.getAnyName();
+    public void cancelByUser(final AnyLifecycleEvent<Any<?>> event) {
+        if (AuthContextUtils.getDomain().equals(event.getDomain())
+                && event.getType() == SyncDeltaType.DELETE
+                && event.getAny() instanceof User) {
+
+            User user = (User) event.getAny();
             engine.getRuntimeService().createNativeProcessInstanceQuery().
-                    sql(createProcessInstanceQuery(event.getAnyKey()).toString()).
-                    list().forEach(procInst -> {
-                        engine.getRuntimeService().deleteProcessInstance(
-                                procInst.getId(), "Cascade Delete user " + username);
-                    });
+                    sql(createProcessInstanceQuery(user.getKey()).toString()).
+                    list().forEach(procInst -> engine.getRuntimeService().deleteProcessInstance(
+                    procInst.getId(), "Cascade Delete user " + user.getUsername()));
         }
     }
 
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java
index fa8dd586aa..00fee3016f 100644
--- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java
@@ -38,6 +38,7 @@ import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
 import org.apache.syncope.core.flowable.support.DomainProcessEngine;
+import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
 import org.apache.syncope.core.workflow.api.WorkflowException;
 import org.apache.syncope.core.workflow.java.AbstractUserWorkflowAdapter;
 import org.flowable.bpmn.model.FlowElement;
@@ -47,8 +48,10 @@ import org.flowable.bpmn.model.Process;
 import org.flowable.common.engine.api.FlowableException;
 import org.flowable.engine.runtime.ProcessInstance;
 import org.flowable.task.api.Task;
+import org.identityconnectors.framework.common.objects.SyncDeltaType;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
 
 public class FlowableUserWorkflowAdapter extends AbstractUserWorkflowAdapter implements WorkflowTaskManager {
 
@@ -58,6 +61,9 @@ public class FlowableUserWorkflowAdapter extends AbstractUserWorkflowAdapter imp
     @Autowired
     protected UserRequestHandler userRequestHandler;
 
+    @Autowired
+    protected ApplicationEventPublisher publisher;
+
     @Override
     public String getPrefix() {
         return "ACT_";
@@ -130,6 +136,9 @@ public class FlowableUserWorkflowAdapter extends AbstractUserWorkflowAdapter imp
         FlowableRuntimeUtils.updateStatus(engine, procInst.getProcessInstanceId(), user);
         User created = userDAO.save(user);
 
+        publisher.publishEvent(
+                new AnyLifecycleEvent<>(this, SyncDeltaType.CREATE, created, AuthContextUtils.getDomain()));
+
         engine.getRuntimeService().updateBusinessKey(
                 procInst.getProcessInstanceId(), FlowableRuntimeUtils.getWFProcBusinessKey(created.getKey()));
 
@@ -219,6 +228,9 @@ public class FlowableUserWorkflowAdapter extends AbstractUserWorkflowAdapter imp
         FlowableRuntimeUtils.updateStatus(engine, procInstID, user);
         User updated = userDAO.save(user);
 
+        publisher.publishEvent(
+                new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated, AuthContextUtils.getDomain()));
+
         variables.keySet().forEach(key -> engine.getRuntimeService().removeVariable(procInstID, key));
         engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER);
         engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.WF_EXECUTOR);
@@ -252,6 +264,9 @@ public class FlowableUserWorkflowAdapter extends AbstractUserWorkflowAdapter imp
         FlowableRuntimeUtils.updateStatus(engine, procInstID, user);
         User updated = userDAO.save(user);
 
+        publisher.publishEvent(
+                new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated, AuthContextUtils.getDomain()));
+
         engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER);
         engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.WF_EXECUTOR);
         engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.TASK);
@@ -307,6 +322,9 @@ public class FlowableUserWorkflowAdapter extends AbstractUserWorkflowAdapter imp
         FlowableRuntimeUtils.updateStatus(engine, procInstID, user);
         User updated = userDAO.save(user);
 
+        publisher.publishEvent(
+                new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated, AuthContextUtils.getDomain()));
+
         @SuppressWarnings("unchecked")
         PropagationByResource<String> propByRes = engine.getRuntimeService().getVariable(
                 procInstID, FlowableRuntimeUtils.PROP_BY_RESOURCE, PropagationByResource.class);
@@ -336,6 +354,9 @@ public class FlowableUserWorkflowAdapter extends AbstractUserWorkflowAdapter imp
 
         User updated = userDAO.save(user);
 
+        publisher.publishEvent(
+                new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated, AuthContextUtils.getDomain()));
+
         @SuppressWarnings("unchecked")
         PropagationByResource<String> propByRes = engine.getRuntimeService().getVariable(
                 procInstID, FlowableRuntimeUtils.PROP_BY_RESOURCE, PropagationByResource.class);
@@ -365,7 +386,10 @@ public class FlowableUserWorkflowAdapter extends AbstractUserWorkflowAdapter imp
         String procInstID = FlowableRuntimeUtils.getWFProcInstID(engine, user.getKey());
 
         doExecuteNextTask(procInstID, user, variables);
-        userDAO.save(user);
+        User updated = userDAO.save(user);
+
+        publisher.publishEvent(
+                new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated, AuthContextUtils.getDomain()));
 
         variables.keySet().forEach(key -> engine.getRuntimeService().removeVariable(procInstID, key));
         engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER);
@@ -387,7 +411,10 @@ public class FlowableUserWorkflowAdapter extends AbstractUserWorkflowAdapter imp
 
         Set<String> tasks = doExecuteNextTask(procInstID, user, variables);
 
-        userDAO.save(user);
+        User updated = userDAO.save(user);
+
+        publisher.publishEvent(
+                new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated, AuthContextUtils.getDomain()));
 
         variables.keySet().forEach(key -> engine.getRuntimeService().removeVariable(procInstID, key));
         engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER);
@@ -430,6 +457,9 @@ public class FlowableUserWorkflowAdapter extends AbstractUserWorkflowAdapter imp
 
             userDAO.delete(user.getKey());
 
+            publisher.publishEvent(
+                    new AnyLifecycleEvent<>(this, SyncDeltaType.DELETE, user, AuthContextUtils.getDomain()));
+
             if (!engine.getHistoryService().createHistoricProcessInstanceQuery().
                     processInstanceId(procInstID).list().isEmpty()) {
 
@@ -447,7 +477,10 @@ public class FlowableUserWorkflowAdapter extends AbstractUserWorkflowAdapter imp
                     propByLinkedAccount);
 
             FlowableRuntimeUtils.updateStatus(engine, procInstID, user);
-            userDAO.save(user);
+            User updated = userDAO.save(user);
+
+            publisher.publishEvent(
+                    new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, updated, AuthContextUtils.getDomain()));
 
             engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.TASK);
             engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER);
@@ -469,6 +502,9 @@ public class FlowableUserWorkflowAdapter extends AbstractUserWorkflowAdapter imp
         FlowableRuntimeUtils.updateStatus(engine, procInstID, user);
         user = userDAO.save(user);
 
+        publisher.publishEvent(
+                new AnyLifecycleEvent<>(this, SyncDeltaType.UPDATE, user, AuthContextUtils.getDomain()));
+
         engine.getRuntimeService().setVariable(
                 procInstID, FlowableRuntimeUtils.USER_TO, dataBinder.getUserTO(user, true));
 
@@ -477,6 +513,9 @@ public class FlowableUserWorkflowAdapter extends AbstractUserWorkflowAdapter imp
 
             userDAO.delete(user.getKey());
 
+            publisher.publishEvent(
+                    new AnyLifecycleEvent<>(this, SyncDeltaType.DELETE, user, AuthContextUtils.getDomain()));
+
             if (!engine.getHistoryService().createHistoricProcessInstanceQuery().
                     processInstanceId(procInstID).list().isEmpty()) {