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 bb7f727dc7 [SYNCOPE-1957] Process auxClasses changes before attributes 
during update
bb7f727dc7 is described below

commit bb7f727dc7effa80c186c51b754bd12ad654bafa
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Fri Mar 27 18:26:07 2026 +0100

    [SYNCOPE-1957] Process auxClasses changes before attributes during update
---
 .../core/provisioning/java/data/AnyDataBinder.java |  44 ++++----
 .../java/data/AnyObjectDataBinderImpl.java         |   2 +
 .../java/data/GroupDataBinderImpl.java             |   2 +
 .../provisioning/java/data/UserDataBinderImpl.java |   2 +
 .../core/flowable/FlowableWorkflowContext.java     |   4 +-
 .../apache/syncope/core/flowable/task/Update.java  |   8 +-
 .../apache/syncope/fit/core/AnyObjectITCase.java   | 113 +++++++++++++++++++++
 7 files changed, 144 insertions(+), 31 deletions(-)

diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyDataBinder.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyDataBinder.java
index 328cb9b564..fd83a10bde 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyDataBinder.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyDataBinder.java
@@ -395,7 +395,25 @@ abstract class AnyDataBinder extends 
AttributableDataBinder {
         }
     }
 
-    @SuppressWarnings({ "unchecked", "rawtypes" })
+    protected void fillAuxClasses(final Relatable<?, ?> any, final AnyUR 
anyUR) {
+        for (StringPatchItem patch : anyUR.getAuxClasses()) {
+            anyTypeClassDAO.findById(patch.getValue()).ifPresentOrElse(
+                    auxClass -> {
+                        switch (patch.getOperation()) {
+                            case ADD_REPLACE:
+                                any.add(auxClass);
+                                break;
+
+                            case DELETE:
+                            default:
+                                any.getAuxClasses().remove(auxClass);
+                        }
+                    },
+                    () -> LOG.debug("Invalid {} {}, ignoring...",
+                            AnyTypeClass.class.getSimpleName(), 
patch.getValue()));
+        }
+    }
+
     protected void fill(
             final AnyTO anyTO,
             final Relatable<?, ?> any,
@@ -444,25 +462,7 @@ abstract class AnyDataBinder extends 
AttributableDataBinder {
         }
         propByRes.merge(managerPropByRes);
 
-        // 1. anyTypeClasses
-        for (StringPatchItem patch : anyUR.getAuxClasses()) {
-            anyTypeClassDAO.findById(patch.getValue()).ifPresentOrElse(
-                    auxClass -> {
-                        switch (patch.getOperation()) {
-                            case ADD_REPLACE:
-                                any.add(auxClass);
-                                break;
-
-                            case DELETE:
-                            default:
-                                any.getAuxClasses().remove(auxClass);
-                        }
-                    },
-                    () -> LOG.debug("Invalid {} {}, ignoring...",
-                            AnyTypeClass.class.getSimpleName(), 
patch.getValue()));
-        }
-
-        // 2. relationships
+        // 1. relationships
         Set<Pair<String, String>> relationships = new HashSet<>();
         for (RelationshipUR patch : anyUR.getRelationships().stream().
                 filter(patch -> patch.getType() != null && 
patch.getOtherEndKey() != null).toList()) {
@@ -522,7 +522,7 @@ abstract class AnyDataBinder extends AttributableDataBinder 
{
             }
         }
 
-        // 3. resources
+        // 2. resources
         for (StringPatchItem patch : anyUR.getResources()) {
             resourceDAO.findById(patch.getValue()).ifPresentOrElse(
                     resource -> {
@@ -543,7 +543,7 @@ abstract class AnyDataBinder extends AttributableDataBinder 
{
         Set<ExternalResource> resources = anyUtils.getAllResources(any);
         SyncopeClientException invalidValues = 
SyncopeClientException.build(ClientExceptionType.InvalidValues);
 
-        // 4. attributes
+        // 3. attributes
         anyUR.getPlainAttrs().stream().filter(patch -> patch.getAttr() != 
null).
                 forEach(patch -> 
getPlainSchema(patch.getAttr().getSchema()).ifPresentOrElse(
                 schema -> {
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
index 5b9aa4a478..a6215ca8dd 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
@@ -203,6 +203,8 @@ public class AnyObjectDataBinderImpl extends AnyDataBinder 
implements AnyObjectD
 
     @Override
     public PropagationByResource<String> update(final AnyObject toBeUpdated, 
final AnyObjectUR anyObjectUR) {
+        fillAuxClasses(toBeUpdated, anyObjectUR);
+
         // Re-merge any pending change from workflow tasks
         AnyObject anyObject = anyObjectDAO.save(toBeUpdated);
 
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
index 86fb42b166..f7d4c72187 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
@@ -169,6 +169,8 @@ public class GroupDataBinderImpl extends AnyDataBinder 
implements GroupDataBinde
 
     @Override
     public PropagationByResource<String> update(final Group toBeUpdated, final 
GroupUR groupUR) {
+        fillAuxClasses(toBeUpdated, groupUR);
+
         // Re-merge any pending change from workflow tasks
         Group group = groupDAO.save(toBeUpdated);
 
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
index 25f958512d..92ea5968df 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
@@ -342,6 +342,8 @@ public class UserDataBinderImpl extends AnyDataBinder 
implements UserDataBinder
 
     @Override
     public UserWorkflowResult.PropagationInfo update(final User toBeUpdated, 
final UserUR userUR) {
+        fillAuxClasses(toBeUpdated, userUR);
+
         // Re-merge any pending change from workflow tasks
         User user = userDAO.save(toBeUpdated);
 
diff --git 
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/FlowableWorkflowContext.java
 
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/FlowableWorkflowContext.java
index b821143b37..1b1a643cfa 100644
--- 
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/FlowableWorkflowContext.java
+++ 
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/FlowableWorkflowContext.java
@@ -248,7 +248,7 @@ public class FlowableWorkflowContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public Update update(final UserDataBinder userDataBinder, final UserDAO 
userDAO) {
-        return new Update(userDataBinder, userDAO);
+    public Update update(final UserDataBinder userDataBinder) {
+        return new Update(userDataBinder);
     }
 }
diff --git 
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Update.java
 
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Update.java
index 539d12b49b..2fda104efb 100644
--- 
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Update.java
+++ 
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Update.java
@@ -20,7 +20,6 @@ package org.apache.syncope.core.flowable.task;
 
 import org.apache.syncope.common.lib.request.UserUR;
 import org.apache.syncope.core.flowable.impl.FlowableRuntimeUtils;
-import org.apache.syncope.core.persistence.api.dao.UserDAO;
 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.data.UserDataBinder;
@@ -30,11 +29,8 @@ public class Update extends FlowableServiceTask {
 
     protected final UserDataBinder dataBinder;
 
-    protected final UserDAO userDAO;
-
-    public Update(final UserDataBinder dataBinder, final UserDAO userDAO) {
+    public Update(final UserDataBinder dataBinder) {
         this.dataBinder = dataBinder;
-        this.userDAO = userDAO;
     }
 
     @Override
@@ -45,8 +41,6 @@ public class Update extends FlowableServiceTask {
         } else {
             User user = execution.getVariable(FlowableRuntimeUtils.USER, 
User.class);
 
-            user = userDAO.save(user);
-
             UserWorkflowResult.PropagationInfo propInfo = 
dataBinder.update(user, req);
 
             // report updated user and propagation by resource as result
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AnyObjectITCase.java
 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AnyObjectITCase.java
index d011ca8189..4b7494d916 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AnyObjectITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AnyObjectITCase.java
@@ -25,22 +25,37 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
 import jakarta.ws.rs.core.Response;
+import java.util.List;
 import java.util.Set;
 import java.util.UUID;
+import java.util.function.Consumer;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.Attr;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.request.AnyCR;
 import org.apache.syncope.common.lib.request.AnyObjectCR;
 import org.apache.syncope.common.lib.request.AnyObjectUR;
+import org.apache.syncope.common.lib.request.AnyUR;
+import org.apache.syncope.common.lib.request.GroupCR;
+import org.apache.syncope.common.lib.request.GroupUR;
 import org.apache.syncope.common.lib.request.StringPatchItem;
+import org.apache.syncope.common.lib.request.UserCR;
+import org.apache.syncope.common.lib.request.UserUR;
 import org.apache.syncope.common.lib.to.AnyObjectTO;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.to.AnyTypeClassTO;
 import org.apache.syncope.common.lib.to.ConnObject;
+import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.PagedResult;
+import org.apache.syncope.common.lib.to.PlainSchemaTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.SchemaType;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
+import org.apache.syncope.common.rest.api.service.AnyTypeClassService;
 import org.apache.syncope.fit.AbstractITCase;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -211,4 +226,102 @@ public class AnyObjectITCase extends AbstractITCase {
         assertFalse(printer.getResources().contains(RESOURCE_NAME_DBSCRIPTED), 
"Should not contain removed resources");
         assertFalse(printer.getAuxClasses().contains("csv"), "Should not 
contain removed auxiliary classes");
     }
+
+    @Test
+    public void issueSYNCOPE1957() {
+        // prepare
+        PlainSchemaTO schema1 = new PlainSchemaTO();
+        schema1.setKey("schema1" + getUUIDString());
+        schema1.setType(AttrSchemaType.Boolean);
+        createSchema(SchemaType.PLAIN, schema1);
+
+        PlainSchemaTO schema2 = new PlainSchemaTO();
+        schema2.setKey("schema2" + getUUIDString());
+        schema2.setType(AttrSchemaType.Boolean);
+        createSchema(SchemaType.PLAIN, schema2);
+
+        String class1Key = "class1" + getUUIDString();
+        AnyTypeClassTO class1 = new AnyTypeClassTO();
+        class1.setKey(class1Key);
+        class1.getPlainSchemas().add(schema1.getKey());
+        class1.getPlainSchemas().add(schema2.getKey());
+
+        Response response = ANY_TYPE_CLASS_SERVICE.create(class1);
+        assertEquals(Response.Status.CREATED.getStatusCode(), 
response.getStatusInfo().getStatusCode());
+
+        class1 = getObject(response.getLocation(), AnyTypeClassService.class, 
AnyTypeClassTO.class);
+
+        // 1. create user, group and printer with auxClass class1
+        Consumer<AnyCR> setupAnyCR = anyCR -> {
+            anyCR.getResources().clear();
+            anyCR.getAuxClasses().add(class1Key);
+            anyCR.getPlainAttrs().add(attr(schema1.getKey(), "true"));
+            anyCR.getPlainAttrs().add(attr(schema2.getKey(), "true"));
+        };
+        Consumer<AnyTO> checkAnyTO = anyTO -> {
+            assertTrue(anyTO.getPlainAttr(schema1.getKey()).isPresent());
+            assertTrue(anyTO.getPlainAttr(schema2.getKey()).isPresent());
+        };
+
+        UserCR userCR = UserITCase.getUniqueSample("[email protected]");
+        setupAnyCR.accept(userCR);
+
+        UserTO user = createUser(userCR).getEntity();
+        checkAnyTO.accept(user);
+
+        GroupCR groupCR = GroupITCase.getSample("syncope1957");
+        setupAnyCR.accept(groupCR);
+
+        GroupTO group = createGroup(groupCR).getEntity();
+        checkAnyTO.accept(group);
+
+        AnyObjectCR printerCR = getSample("syncope1957");
+        setupAnyCR.accept(printerCR);
+
+        AnyObjectTO printer = createAnyObject(printerCR).getEntity();
+        checkAnyTO.accept(printer);
+
+        // 2. create new anytypeclass and move schema there
+        class1.getPlainSchemas().remove(schema2.getKey());
+        ANY_TYPE_CLASS_SERVICE.update(class1);
+
+        class1 = ANY_TYPE_CLASS_SERVICE.read(class1.getKey());
+        assertEquals(List.of(schema1.getKey()), class1.getPlainSchemas());
+
+        String class2Key = "class2" + getUUIDString();
+        AnyTypeClassTO class2 = new AnyTypeClassTO();
+        class2.setKey(class2Key);
+        class2.getPlainSchemas().add(schema2.getKey());
+
+        response = ANY_TYPE_CLASS_SERVICE.create(class2);
+        assertEquals(Response.Status.CREATED.getStatusCode(), 
response.getStatusInfo().getStatusCode());
+
+        class2 = getObject(response.getLocation(), AnyTypeClassService.class, 
AnyTypeClassTO.class);
+        assertEquals(List.of(schema2.getKey()), class2.getPlainSchemas());
+
+        // 3. update user, group and printer by adding auxClass class2
+        Consumer<AnyUR> setupAnyUR = anyUR -> anyUR.getAuxClasses().
+                add(new StringPatchItem.Builder().value(class2Key).build());
+
+        UserUR userUR = new UserUR();
+        userUR.setKey(user.getKey());
+        setupAnyUR.accept(userUR);
+
+        user = updateUser(userUR).getEntity();
+        checkAnyTO.accept(user);
+
+        GroupUR groupUR = new GroupUR();
+        groupUR.setKey(group.getKey());
+        setupAnyUR.accept(groupUR);
+
+        group = updateGroup(groupUR).getEntity();
+        checkAnyTO.accept(group);
+
+        AnyObjectUR printerUR = new AnyObjectUR();
+        printerUR.setKey(printer.getKey());
+        setupAnyUR.accept(printerUR);
+
+        printer = updateAnyObject(printerUR).getEntity();
+        checkAnyTO.accept(printer);
+    }
 }

Reply via email to